@mytegroupinc/myte-core 0.0.7 → 0.0.8

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/cli.js +316 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,19 +5,25 @@ Internal implementation package for the `myte` CLI.
5
5
  Most users should install the unscoped wrapper instead:
6
6
  - `npm install myte` then `npx myte bootstrap`
7
7
  - `npm install myte` then `npx myte sync-qaqc`
8
+ - `npm install myte` then `npx myte feedback-sync`
8
9
  - `npm install myte` then `npx myte query "..." --with-diff`
9
10
  - `npm install myte` then `npm exec myte -- query "..." --with-diff`
10
11
  - `npm install myte` then `npx myte update-team "Backend deploy completed; QAQC rerun queued."`
12
+ - `npm install myte` then `npx myte update-owner --subject "QAQC progress" --body-file ./updates/owner.md`
11
13
  - `npm install myte` then `npx myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
12
14
  - `npm i -g myte` then `myte bootstrap`
13
15
  - `npm i -g myte` then `myte sync-qaqc`
16
+ - `npm i -g myte` then `myte feedback-sync`
14
17
  - `npm i -g myte` then `myte query "..." --with-diff`
15
18
  - `npm i -g myte` then `myte update-team "Backend deploy completed; QAQC rerun queued."`
19
+ - `npm i -g myte` then `myte update-owner --subject "QAQC progress" --body-file ./updates/owner.md`
16
20
  - `npm i -g myte` then `myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
17
21
  - `npx myte@latest bootstrap`
18
22
  - `npx myte@latest sync-qaqc`
23
+ - `npx myte@latest feedback-sync`
19
24
  - `npx myte@latest query "..." --with-diff`
20
25
  - `npx myte@latest update-team "Backend deploy completed; QAQC rerun queued."`
26
+ - `npx myte@latest update-owner --subject "QAQC progress" --body-file ./updates/owner.md`
21
27
  - `npx myte@latest update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
22
28
  - `npm install myte` then `npx myte create-prd ./drafts/auth-prd.md`
23
29
  - `cat ./drafts/auth-prd.md | npx myte create-prd --stdin`
@@ -41,8 +47,13 @@ Notes:
41
47
  - `sync-qaqc` writes active mission QAQC cards to `MyteCommandCenter/data/qaqc/active-missions` and refreshes matching `MyteCommandCenter/data/missions` cards.
42
48
  - `sync-qaqc` only exports active `Todo` / `In Progress` missions plus a public QAQC summary and sanitized latest batch metadata.
43
49
  - `sync-qaqc` removes previously QAQC-managed mission files from `MyteCommandCenter/data/missions` once they leave the active set.
50
+ - `feedback-sync` writes public feedback cards under `MyteCommandCenter/data/feedback/items`.
51
+ - `feedback-sync` writes readable PRD copies under `MyteCommandCenter/data/feedback/prds` when PRD text exists.
52
+ - `feedback-sync` fully replaces the feedback-owned sync folders to avoid stale local feedback noise.
44
53
  - `create-prd` is a deterministic PRD upload path, not an LLM generation command.
45
54
  - `update-team` creates a project comment through `/api/project-assistant/project-comment`.
55
+ - `update-owner` sends a direct owner email through `/api/project-assistant/update-owner`.
56
+ - `update-owner` requires `--subject` plus body markdown from `--body-markdown`, `--body-file`, positional input, or `--stdin`.
46
57
  - `update-client` creates a client update draft through `/api/project-assistant/client-update-drafts`.
47
58
  - `update-client` requires `--subject` plus body markdown from `--body-markdown`, `--body-file`, positional input, or `--stdin`.
48
59
  - `update-client` accepts optional `--target-contact-id` repeats or `--target-contact-ids <id1,id2>`.
@@ -64,11 +75,13 @@ Deterministic `create-prd` contract:
64
75
  Examples:
65
76
  - `npx myte bootstrap`
66
77
  - `npx myte sync-qaqc`
78
+ - `npx myte feedback-sync`
67
79
  - `npx myte bootstrap --dry-run --json`
68
80
  - `npx myte sync-qaqc --dry-run --json`
69
81
  - `npx myte create-prd ./drafts/auth-prd.md --description "Short card summary"`
70
82
  - `npx myte create-prd ./drafts/auth-prd.md --print-context`
71
83
  - `npx myte update-team "Backend deploy completed; QAQC rerun queued."`
84
+ - `npx myte update-owner --subject "QAQC progress" --body-file ./updates/owner.md`
72
85
  - `npx myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
73
86
 
74
87
  This package is published under the org scope for governance; the public `myte` wrapper delegates here.
package/cli.js CHANGED
@@ -61,7 +61,9 @@ function splitCommand(argv) {
61
61
  "add-prd",
62
62
  "prd",
63
63
  "update-team",
64
+ "update-owner",
64
65
  "update-client",
66
+ "feedback-sync",
65
67
  "help",
66
68
  "--help",
67
69
  "-h",
@@ -78,7 +80,7 @@ function parseArgs(argv) {
78
80
  try {
79
81
  // eslint-disable-next-line global-require
80
82
  return require("minimist")(argv, {
81
- boolean: ["with-diff", "diff", "print-context", "dry-run", "fetch", "json", "stdin"],
83
+ boolean: ["with-diff", "diff", "print-context", "dry-run", "fetch", "json", "stdin", "with-prd-text"],
82
84
  string: [
83
85
  "query",
84
86
  "q",
@@ -97,6 +99,8 @@ function parseArgs(argv) {
97
99
  "body-file",
98
100
  "target-contact-id",
99
101
  "target-contact-ids",
102
+ "status",
103
+ "source",
100
104
  ],
101
105
  alias: {
102
106
  q: "query",
@@ -153,11 +157,14 @@ function printHelp() {
153
157
  " myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
154
158
  " myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
155
159
  " myte update-team \"<content>\" [--json]",
160
+ " myte update-owner --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--json]",
156
161
  " myte update-client --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--target-contact-ids <id1,id2>] [--json]",
162
+ " myte feedback-sync [--status Pending] [--source User] [--output-dir ./MyteCommandCenter] [--json]",
157
163
  " myte chat",
158
164
  " myte create-prd <file.md> [--json] [--title \"...\"] [--description \"...\"]",
159
165
  " myte add-prd <file.md> [--json]",
160
166
  " cat file.md | myte create-prd --stdin [--title \"...\"] [--description \"...\"]",
167
+ " cat update.md | myte update-owner --stdin --subject \"Owner update\"",
161
168
  " cat update.md | myte update-client --stdin --subject \"Weekly client update\"",
162
169
  "",
163
170
  "Run forms:",
@@ -190,11 +197,21 @@ function printHelp() {
190
197
  " - Creates a project team comment through /api/project-assistant/project-comment",
191
198
  " - Required: content (inline, --content, or --stdin)",
192
199
  "",
200
+ "update-owner contract:",
201
+ " - Sends a direct owner email through /api/project-assistant/update-owner",
202
+ " - Required: --subject and body markdown (via --body-markdown, --body-file, positional file/text, or --stdin)",
203
+ " - Uses Myte SMTP, resolves the owner from the project, and stores a durable send record",
204
+ "",
193
205
  "update-client contract:",
194
206
  " - Creates a client update draft through /api/project-assistant/client-update-drafts",
195
207
  " - Required: --subject and body markdown (via --body-markdown, --body-file, positional file/text, or --stdin)",
196
208
  " - Optional: --target-contact-id <id> (repeatable) or --target-contact-ids <id1,id2>",
197
209
  "",
210
+ "feedback-sync contract:",
211
+ " - Runs from the wrapper root that contains the project's configured repo folders",
212
+ " - Writes project feedback cards under MyteCommandCenter/data/feedback/items",
213
+ " - Writes PRD text copies under MyteCommandCenter/data/feedback/prds when available",
214
+ "",
198
215
  "Options:",
199
216
  " --with-diff Include deterministic git diffs (project-scoped)",
200
217
  " --diff-limit <chars> Truncate diff context to N chars (default: 200000)",
@@ -205,11 +222,14 @@ function printHelp() {
205
222
  " --title <text> Override PRD title for raw markdown uploads",
206
223
  " --description <text> Set feedback description/card summary for raw markdown uploads",
207
224
  " --content <text> Team update content for update-team",
208
- " --subject <text> Client update subject for update-client",
209
- " --body-markdown <md> Client update markdown body for update-client",
210
- " --body-file <path> Read client update markdown body from a file",
225
+ " --subject <text> Subject for update-owner or update-client",
226
+ " --body-markdown <md> Markdown body for update-owner or update-client",
227
+ " --body-file <path> Read update-owner or update-client markdown body from a file",
211
228
  " --target-contact-id Add one client contact ObjectId (repeatable)",
212
229
  " --target-contact-ids Comma-separated client contact ObjectIds",
230
+ " --status <value> Feedback status filter for feedback-sync (default: Pending)",
231
+ " --source <value> Feedback source filter for feedback-sync",
232
+ " --with-prd-text Include extracted PRD text in feedback-sync (default: on)",
213
233
  " --print-context Print JSON payload and exit (no query call)",
214
234
  " --no-fetch Don't git fetch origin main/master before diff",
215
235
  "",
@@ -219,7 +239,9 @@ function printHelp() {
219
239
  " myte bootstrap --output-dir ./MyteCommandCenter",
220
240
  " myte sync-qaqc",
221
241
  " myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
242
+ " myte update-owner --subject \"QAQC progress\" --body-file ./updates/owner.md",
222
243
  " myte update-client --subject \"Weekly client update\" --body-file ./updates/week-12.md",
244
+ " myte feedback-sync --status Pending --source User",
223
245
  " myte update-client --subject \"Weekly client update\" --body-markdown \"## Progress\\n- Login complete\" --target-contact-ids 507f1f77bcf86cd799439011,507f1f77bcf86cd799439012",
224
246
  " myte create-prd ./drafts/auth-prd.md --description \"Short card summary\"",
225
247
  " cat ./drafts/auth-prd.md | myte create-prd --stdin",
@@ -377,6 +399,15 @@ async function resolveClientUpdateBody(args) {
377
399
  process.exit(1);
378
400
  }
379
401
 
402
+ async function resolveOwnerUpdateBody(args) {
403
+ const body = await resolveClientUpdateBody(args);
404
+ if (!body) {
405
+ console.error("Owner update body_markdown is empty.");
406
+ process.exit(1);
407
+ }
408
+ return body;
409
+ }
410
+
380
411
  function resolveTargetContactIds(args) {
381
412
  return dedupeStrings(
382
413
  parseCsvValues(
@@ -951,6 +982,33 @@ async function fetchQaqcSyncSnapshot({ apiBase, key, timeoutMs }) {
951
982
  return body.data || {};
952
983
  }
953
984
 
985
+ async function fetchFeedbackSyncSnapshot({ apiBase, key, timeoutMs, filters = {} }) {
986
+ const fetchFn = await getFetch();
987
+ const url = new URL(`${apiBase}/project-assistant/feedback-sync`);
988
+ if (filters.status) url.searchParams.set("status", String(filters.status));
989
+ if (filters.source) url.searchParams.set("source", String(filters.source));
990
+ if (filters.includePrdText !== undefined) {
991
+ url.searchParams.set("include_prd_text", filters.includePrdText ? "true" : "false");
992
+ }
993
+ const { resp, body } = await fetchJsonWithTimeout(
994
+ fetchFn,
995
+ url.toString(),
996
+ {
997
+ method: "GET",
998
+ headers: { Authorization: `Bearer ${key}` },
999
+ },
1000
+ timeoutMs
1001
+ );
1002
+
1003
+ if (!resp.ok || body.status !== "success") {
1004
+ const msg = body?.message || `Feedback sync request failed (${resp.status})`;
1005
+ const err = new Error(msg);
1006
+ err.status = resp.status;
1007
+ throw err;
1008
+ }
1009
+ return body.data || {};
1010
+ }
1011
+
954
1012
  async function callAssistantQuery({ apiBase, key, payload, timeoutMs, endpoint = "/project-assistant/query" }) {
955
1013
  const fetchFn = await getFetch();
956
1014
  const url = `${apiBase}${endpoint}`;
@@ -1042,6 +1100,74 @@ async function runUpdateTeam(args) {
1042
1100
  }
1043
1101
  }
1044
1102
 
1103
+ async function runUpdateOwner(args) {
1104
+ const key = getProjectApiKey();
1105
+ if (!key) {
1106
+ console.error("Missing MYTE_API_KEY (project key) in environment/.env");
1107
+ process.exit(1);
1108
+ }
1109
+
1110
+ const subject = firstNonEmptyString(args.subject);
1111
+ if (!subject) {
1112
+ console.error("Missing --subject for owner update.");
1113
+ printHelp();
1114
+ process.exit(1);
1115
+ }
1116
+
1117
+ const payload = {
1118
+ subject,
1119
+ body_markdown: await resolveOwnerUpdateBody(args),
1120
+ };
1121
+
1122
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
1123
+ console.log(JSON.stringify(payload, null, 2));
1124
+ return;
1125
+ }
1126
+
1127
+ const timeoutMs = resolveTimeoutMs(args);
1128
+ const apiBase = resolveApiBase(args);
1129
+
1130
+ let data;
1131
+ try {
1132
+ data = await callAssistantQuery({
1133
+ apiBase,
1134
+ key,
1135
+ payload,
1136
+ timeoutMs,
1137
+ endpoint: "/project-assistant/update-owner",
1138
+ });
1139
+ } catch (err) {
1140
+ if (err?.name === "AbortError") {
1141
+ console.error(`Request timed out after ${timeoutMs}ms`);
1142
+ } else {
1143
+ console.error("Owner update failed:", err?.message || err);
1144
+ }
1145
+ process.exit(1);
1146
+ }
1147
+
1148
+ if (args.json) {
1149
+ console.log(JSON.stringify(data, null, 2));
1150
+ return;
1151
+ }
1152
+
1153
+ if (data.update_id) console.log(`Update ID: ${data.update_id}`);
1154
+ if (data.project_title) console.log(`Project: ${data.project_title}`);
1155
+ if (data.project_id) console.log(`Project ID: ${data.project_id}`);
1156
+ if (data.status) console.log(`Status: ${data.status}`);
1157
+ if (data.subject) console.log(`Subject: ${data.subject}`);
1158
+ if (data.owner_name || data.owner_email) {
1159
+ console.log(`Owner: ${data.owner_name || data.owner_email}${data.owner_email && data.owner_name ? ` <${data.owner_email}>` : ""}`);
1160
+ }
1161
+ if (data.sender_name || data.sender_email) {
1162
+ console.log(`Sender: ${data.sender_name || data.sender_email}${data.sender_email && data.sender_name ? ` <${data.sender_email}>` : ""}`);
1163
+ }
1164
+ if (data.sent_at) console.log(`Sent At: ${data.sent_at}`);
1165
+ if (data.snippet) {
1166
+ console.log("Snippet:");
1167
+ console.log(data.snippet);
1168
+ }
1169
+ }
1170
+
1045
1171
  async function runUpdateClient(args) {
1046
1172
  const key = getProjectApiKey();
1047
1173
  if (!key) {
@@ -1125,6 +1251,20 @@ function clearYamlDirectory(dirPath) {
1125
1251
  }
1126
1252
  }
1127
1253
 
1254
+ function clearFileDirectory(dirPath, extensions) {
1255
+ const normalized = new Set((Array.isArray(extensions) ? extensions : []).map((ext) => String(ext || "").toLowerCase()));
1256
+ if (!fs.existsSync(dirPath)) {
1257
+ fs.mkdirSync(dirPath, { recursive: true });
1258
+ return;
1259
+ }
1260
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
1261
+ if (!entry.isFile()) continue;
1262
+ const ext = path.extname(entry.name).toLowerCase();
1263
+ if (normalized.size && !normalized.has(ext)) continue;
1264
+ fs.rmSync(path.join(dirPath, entry.name), { force: true });
1265
+ }
1266
+ }
1267
+
1128
1268
  function stableItemId(item, keys, fallback) {
1129
1269
  for (const key of keys) {
1130
1270
  const value = String(item?.[key] || "").trim();
@@ -1149,6 +1289,11 @@ function writeJsonFile(filePath, value) {
1149
1289
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
1150
1290
  }
1151
1291
 
1292
+ function writeTextFile(filePath, value) {
1293
+ ensureDir(path.dirname(filePath));
1294
+ fs.writeFileSync(filePath, String(value || ""), "utf8");
1295
+ }
1296
+
1152
1297
  function readJsonFile(filePath) {
1153
1298
  if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return null;
1154
1299
  try {
@@ -1324,6 +1469,66 @@ function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
1324
1469
  };
1325
1470
  }
1326
1471
 
1472
+ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
1473
+ const targetRoot = outputDir
1474
+ ? path.resolve(process.cwd(), String(outputDir))
1475
+ : path.join(wrapperRoot, "MyteCommandCenter");
1476
+ const dataRoot = path.join(targetRoot, "data");
1477
+ const feedbackRoot = path.join(dataRoot, "feedback");
1478
+ const itemsDir = path.join(feedbackRoot, "items");
1479
+ const prdsDir = path.join(feedbackRoot, "prds");
1480
+
1481
+ ensureDir(dataRoot);
1482
+ ensureDir(feedbackRoot);
1483
+ clearYamlDirectory(itemsDir);
1484
+ clearFileDirectory(prdsDir, [".md"]);
1485
+
1486
+ const items = Array.isArray(snapshot.items) ? snapshot.items.map((item) => scrubBootstrapValue(item)) : [];
1487
+ const itemIds = [];
1488
+ let prdCount = 0;
1489
+
1490
+ if (snapshot.project && typeof snapshot.project === "object") {
1491
+ writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
1492
+ }
1493
+
1494
+ items.forEach((item, index) => {
1495
+ const feedbackId = stableItemId(item, ["feedback_id", "id"], `F${String(index + 1).padStart(3, "0")}`);
1496
+ itemIds.push(feedbackId);
1497
+ const card = scrubBootstrapValue(item);
1498
+ const prdText = String(card.prd_text || "").trim();
1499
+ if (prdText) {
1500
+ delete card.prd_text;
1501
+ card.prd_file = `prds/${feedbackId}.md`;
1502
+ writeTextFile(path.join(prdsDir, `${feedbackId}.md`), `${prdText}\n`);
1503
+ prdCount += 1;
1504
+ }
1505
+ writeYamlFile(path.join(itemsDir, `${feedbackId}.yml`), card);
1506
+ });
1507
+
1508
+ const manifest = {
1509
+ schema_version: snapshot.schema_version || 1,
1510
+ generated_at: snapshot.generated_at || null,
1511
+ snapshot_hash: snapshot.snapshot_hash || null,
1512
+ project: snapshot.project ? scrubBootstrapValue(snapshot.project) : null,
1513
+ repo_names: Array.isArray(snapshot.repo_names) ? snapshot.repo_names : [],
1514
+ filters: snapshot.filters && typeof snapshot.filters === "object" ? scrubBootstrapValue(snapshot.filters) : {},
1515
+ item_ids: itemIds,
1516
+ counts: snapshot.counts && typeof snapshot.counts === "object"
1517
+ ? scrubBootstrapValue(snapshot.counts)
1518
+ : { total_feedback: items.length, with_prd_text: prdCount },
1519
+ };
1520
+ if (snapshot.pagination && typeof snapshot.pagination === "object") {
1521
+ manifest.pagination = scrubBootstrapValue(snapshot.pagination);
1522
+ }
1523
+ writeJsonFile(path.join(feedbackRoot, "manifest.json"), manifest);
1524
+
1525
+ return {
1526
+ targetRoot,
1527
+ dataRoot,
1528
+ manifest,
1529
+ };
1530
+ }
1531
+
1327
1532
  async function runCreatePrd(args) {
1328
1533
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
1329
1534
  if (!key) {
@@ -1667,6 +1872,103 @@ async function runSyncQaqc(args) {
1667
1872
  console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
1668
1873
  }
1669
1874
 
1875
+ async function runFeedbackSync(args) {
1876
+ const key = getProjectApiKey();
1877
+ if (!key) {
1878
+ console.error("Missing MYTE_API_KEY (project key) in environment/.env");
1879
+ process.exit(1);
1880
+ }
1881
+
1882
+ const timeoutMs = resolveTimeoutMs(args);
1883
+ const apiBase = resolveApiBase(args);
1884
+ const includePrdText = args["with-prd-text"] !== undefined
1885
+ ? Boolean(args["with-prd-text"])
1886
+ : args.withPrdText !== undefined
1887
+ ? Boolean(args.withPrdText)
1888
+ : true;
1889
+ const filters = {
1890
+ status: firstNonEmptyString(args.status) || "Pending",
1891
+ source: firstNonEmptyString(args.source) || "",
1892
+ includePrdText,
1893
+ };
1894
+
1895
+ let snapshot;
1896
+ try {
1897
+ snapshot = await fetchFeedbackSyncSnapshot({ apiBase, key, timeoutMs, filters });
1898
+ } catch (err) {
1899
+ console.error("Failed to fetch feedback sync snapshot:", err?.message || err);
1900
+ process.exit(1);
1901
+ }
1902
+
1903
+ if (args["print-context"] || args.printContext) {
1904
+ console.log(JSON.stringify(snapshot, null, 2));
1905
+ return;
1906
+ }
1907
+
1908
+ let resolved;
1909
+ try {
1910
+ resolved = resolveBootstrapWorkspace(snapshot.repo_names || []);
1911
+ } catch (err) {
1912
+ console.error(err?.message || err);
1913
+ process.exit(1);
1914
+ }
1915
+
1916
+ const wrapperRoot = resolved.root;
1917
+ const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
1918
+ const dryRun = Boolean(args["dry-run"] || args.dryRun);
1919
+ const counts = snapshot.counts && typeof snapshot.counts === "object" ? snapshot.counts : {};
1920
+ const summary = {
1921
+ api_base: apiBase,
1922
+ project_id: snapshot?.project?.id || null,
1923
+ wrapper_root: wrapperRoot,
1924
+ output_root: outputDir ? path.resolve(process.cwd(), String(outputDir)) : path.join(wrapperRoot, "MyteCommandCenter"),
1925
+ repo_names: Array.isArray(snapshot.repo_names) ? snapshot.repo_names : [],
1926
+ local: {
1927
+ mode: resolved.mode,
1928
+ found: (resolved.repos || []).map((repo) => repo.name),
1929
+ missing: resolved.missing || [],
1930
+ },
1931
+ filters: snapshot.filters && typeof snapshot.filters === "object" ? snapshot.filters : filters,
1932
+ counts,
1933
+ snapshot_hash: snapshot.snapshot_hash || null,
1934
+ generated_at: snapshot.generated_at || null,
1935
+ dry_run: dryRun,
1936
+ };
1937
+
1938
+ if (dryRun) {
1939
+ if (args.json) {
1940
+ console.log(JSON.stringify(summary, null, 2));
1941
+ } else {
1942
+ console.log(`Project: ${summary.project_id || "(unknown)"}`);
1943
+ console.log(`Wrapper root: ${summary.wrapper_root}`);
1944
+ console.log(`Output root: ${summary.output_root}`);
1945
+ console.log(`Configured repos: ${summary.repo_names.join(", ") || "(none)"}`);
1946
+ console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
1947
+ if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
1948
+ console.log(`Counts: total_feedback=${summary.counts.total_feedback || 0}, with_prd_text=${summary.counts.with_prd_text || 0}`);
1949
+ console.log("Dry run only - no files written.");
1950
+ }
1951
+ return;
1952
+ }
1953
+
1954
+ const writeResult = writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir });
1955
+ summary.data_root = writeResult.dataRoot;
1956
+
1957
+ if (args.json) {
1958
+ console.log(JSON.stringify(summary, null, 2));
1959
+ return;
1960
+ }
1961
+
1962
+ console.log(`Project: ${summary.project_id || "(unknown)"}`);
1963
+ console.log(`Wrapper root: ${summary.wrapper_root}`);
1964
+ console.log(`Output root: ${summary.output_root}`);
1965
+ console.log(`Configured repos: ${summary.repo_names.join(", ") || "(none)"}`);
1966
+ console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
1967
+ if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
1968
+ console.log(`Wrote feedback: total_feedback=${summary.counts.total_feedback || 0}, with_prd_text=${summary.counts.with_prd_text || 0}`);
1969
+ console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
1970
+ }
1971
+
1670
1972
  async function runQuery(args) {
1671
1973
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
1672
1974
  if (!key) {
@@ -1810,6 +2112,11 @@ async function main() {
1810
2112
  return;
1811
2113
  }
1812
2114
 
2115
+ if (command === "feedback-sync") {
2116
+ await runFeedbackSync(args);
2117
+ return;
2118
+ }
2119
+
1813
2120
  if (command === "chat") {
1814
2121
  await runChat(args);
1815
2122
  return;
@@ -1825,6 +2132,11 @@ async function main() {
1825
2132
  return;
1826
2133
  }
1827
2134
 
2135
+ if (command === "update-owner") {
2136
+ await runUpdateOwner(args);
2137
+ return;
2138
+ }
2139
+
1828
2140
  if (command === "update-client") {
1829
2141
  await runUpdateClient(args);
1830
2142
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Myte CLI core implementation (Project Assistant + deterministic diffs).",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",