@mytegroupinc/myte-core 0.0.5 → 0.0.6
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 +11 -0
- package/cli.js +234 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,14 @@ Internal implementation package for the `myte` CLI.
|
|
|
4
4
|
|
|
5
5
|
Most users should install the unscoped wrapper instead:
|
|
6
6
|
- `npm install myte` then `npx myte bootstrap`
|
|
7
|
+
- `npm install myte` then `npx myte sync-qaqc`
|
|
7
8
|
- `npm install myte` then `npx myte query "..." --with-diff`
|
|
8
9
|
- `npm install myte` then `npm exec myte -- query "..." --with-diff`
|
|
9
10
|
- `npm i -g myte` then `myte bootstrap`
|
|
11
|
+
- `npm i -g myte` then `myte sync-qaqc`
|
|
10
12
|
- `npm i -g myte` then `myte query "..." --with-diff`
|
|
11
13
|
- `npx myte@latest bootstrap`
|
|
14
|
+
- `npx myte@latest sync-qaqc`
|
|
12
15
|
- `npx myte@latest query "..." --with-diff`
|
|
13
16
|
- `npm install myte` then `npx myte create-prd ./drafts/auth-prd.md`
|
|
14
17
|
- `cat ./drafts/auth-prd.md | npx myte create-prd --stdin`
|
|
@@ -26,6 +29,12 @@ Notes:
|
|
|
26
29
|
- `bootstrap` is a local file materialization path, not a hosted file download.
|
|
27
30
|
- `bootstrap` expects to run from a wrapper root that contains the project's configured repo folders.
|
|
28
31
|
- `bootstrap` writes `MyteCommandCenter/data/phases`, `epics`, `stories`, `missions`, `project.yml`, and `bootstrap-manifest.json`.
|
|
32
|
+
- `bootstrap` materializes a public Command Center DTO, not raw backend documents.
|
|
33
|
+
- `bootstrap` excludes internal keys like `_id`, `org_id`, `project_id`, `created_by`, `assigned_to`, and raw `qa_qc_results`.
|
|
34
|
+
- `sync-qaqc` works without `bootstrap`; it creates `MyteCommandCenter/data/qaqc` automatically if missing.
|
|
35
|
+
- `sync-qaqc` writes active mission QAQC cards to `MyteCommandCenter/data/qaqc/active-missions` and refreshes matching `MyteCommandCenter/data/missions` cards.
|
|
36
|
+
- `sync-qaqc` only exports active `Todo` / `In Progress` missions plus a public QAQC summary and sanitized latest batch metadata.
|
|
37
|
+
- `sync-qaqc` removes previously QAQC-managed mission files from `MyteCommandCenter/data/missions` once they leave the active set.
|
|
29
38
|
- `create-prd` is a deterministic PRD upload path, not an LLM generation command.
|
|
30
39
|
- `--with-diff` only searches repo folders whose names match the project repo names configured in Myte.
|
|
31
40
|
- `--with-diff` includes per-repo diagnostics in `print-context` payload:
|
|
@@ -44,7 +53,9 @@ Deterministic `create-prd` contract:
|
|
|
44
53
|
|
|
45
54
|
Examples:
|
|
46
55
|
- `npx myte bootstrap`
|
|
56
|
+
- `npx myte sync-qaqc`
|
|
47
57
|
- `npx myte bootstrap --dry-run --json`
|
|
58
|
+
- `npx myte sync-qaqc --dry-run --json`
|
|
48
59
|
- `npx myte create-prd ./drafts/auth-prd.md --description "Short card summary"`
|
|
49
60
|
- `npx myte create-prd ./drafts/auth-prd.md --print-context`
|
|
50
61
|
|
package/cli.js
CHANGED
|
@@ -49,7 +49,7 @@ function loadEnv() {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function splitCommand(argv) {
|
|
52
|
-
const known = new Set(["query", "ask", "chat", "config", "bootstrap", "create-prd", "add-prd", "prd", "help", "--help", "-h"]);
|
|
52
|
+
const known = new Set(["query", "ask", "chat", "config", "bootstrap", "sync-qaqc", "qaqc-sync", "create-prd", "add-prd", "prd", "help", "--help", "-h"]);
|
|
53
53
|
const first = argv[0];
|
|
54
54
|
if (first && known.has(first)) {
|
|
55
55
|
const cmd = first === "--help" || first === "-h" ? "help" : first;
|
|
@@ -117,6 +117,7 @@ function printHelp() {
|
|
|
117
117
|
" myte query \"<text>\" [--with-diff] [--context \"...\"]",
|
|
118
118
|
" myte config [--json]",
|
|
119
119
|
" myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
|
|
120
|
+
" myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
|
|
120
121
|
" myte chat",
|
|
121
122
|
" myte create-prd <file.md> [--json] [--title \"...\"] [--description \"...\"]",
|
|
122
123
|
" myte add-prd <file.md> [--json]",
|
|
@@ -136,6 +137,12 @@ function printHelp() {
|
|
|
136
137
|
" - Writes MyteCommandCenter/data/phases, epics, stories, and missions locally",
|
|
137
138
|
" - Uses the project-scoped bootstrap snapshot from the Myte API",
|
|
138
139
|
"",
|
|
140
|
+
"sync-qaqc contract:",
|
|
141
|
+
" - Run from the wrapper root that contains the project's configured repo folders",
|
|
142
|
+
" - Works even if bootstrap has not been run yet; it creates MyteCommandCenter/data/qaqc automatically",
|
|
143
|
+
" - Writes active mission QAQC context under MyteCommandCenter/data/qaqc/active-missions",
|
|
144
|
+
" - Refreshes matching MyteCommandCenter/data/missions cards for active missions only",
|
|
145
|
+
"",
|
|
139
146
|
"create-prd contract:",
|
|
140
147
|
" - Required: valid MYTE_API_KEY, PRD markdown body, title",
|
|
141
148
|
" - Title source: myte-kanban.title, first # heading, or --title",
|
|
@@ -158,6 +165,7 @@ function printHelp() {
|
|
|
158
165
|
" myte query \"What changed in logging?\" --with-diff",
|
|
159
166
|
" myte bootstrap",
|
|
160
167
|
" myte bootstrap --output-dir ./MyteCommandCenter",
|
|
168
|
+
" myte sync-qaqc",
|
|
161
169
|
" myte create-prd ./drafts/auth-prd.md --description \"Short card summary\"",
|
|
162
170
|
" cat ./drafts/auth-prd.md | myte create-prd --stdin",
|
|
163
171
|
" myte config",
|
|
@@ -733,6 +741,28 @@ async function fetchBootstrapSnapshot({ apiBase, key, timeoutMs }) {
|
|
|
733
741
|
return body.data || {};
|
|
734
742
|
}
|
|
735
743
|
|
|
744
|
+
async function fetchQaqcSyncSnapshot({ apiBase, key, timeoutMs }) {
|
|
745
|
+
const fetchFn = await getFetch();
|
|
746
|
+
const url = `${apiBase}/project-assistant/qaqc-sync`;
|
|
747
|
+
const { resp, body } = await fetchJsonWithTimeout(
|
|
748
|
+
fetchFn,
|
|
749
|
+
url,
|
|
750
|
+
{
|
|
751
|
+
method: "GET",
|
|
752
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
753
|
+
},
|
|
754
|
+
timeoutMs
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
if (!resp.ok || body.status !== "success") {
|
|
758
|
+
const msg = body?.message || `QAQC sync request failed (${resp.status})`;
|
|
759
|
+
const err = new Error(msg);
|
|
760
|
+
err.status = resp.status;
|
|
761
|
+
throw err;
|
|
762
|
+
}
|
|
763
|
+
return body.data || {};
|
|
764
|
+
}
|
|
765
|
+
|
|
736
766
|
async function callAssistantQuery({ apiBase, key, payload, timeoutMs, endpoint = "/project-assistant/query" }) {
|
|
737
767
|
const fetchFn = await getFetch();
|
|
738
768
|
const url = `${apiBase}${endpoint}`;
|
|
@@ -799,6 +829,46 @@ function writeJsonFile(filePath, value) {
|
|
|
799
829
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
800
830
|
}
|
|
801
831
|
|
|
832
|
+
function readJsonFile(filePath) {
|
|
833
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return null;
|
|
834
|
+
try {
|
|
835
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
836
|
+
} catch {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const BOOTSTRAP_FORBIDDEN_KEYS = new Set([
|
|
842
|
+
"_id",
|
|
843
|
+
"org_id",
|
|
844
|
+
"project_id",
|
|
845
|
+
"created_by",
|
|
846
|
+
"assigned_to",
|
|
847
|
+
"user_id",
|
|
848
|
+
"qa_qc_results",
|
|
849
|
+
"job_id",
|
|
850
|
+
"job_ids",
|
|
851
|
+
"celery_task_id",
|
|
852
|
+
"conversation_id",
|
|
853
|
+
"error",
|
|
854
|
+
]);
|
|
855
|
+
|
|
856
|
+
function scrubBootstrapValue(value) {
|
|
857
|
+
if (Array.isArray(value)) {
|
|
858
|
+
return value.map((item) => scrubBootstrapValue(item));
|
|
859
|
+
}
|
|
860
|
+
if (!value || typeof value !== "object") {
|
|
861
|
+
return value;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const cleaned = {};
|
|
865
|
+
for (const [key, child] of Object.entries(value)) {
|
|
866
|
+
if (BOOTSTRAP_FORBIDDEN_KEYS.has(key)) continue;
|
|
867
|
+
cleaned[key] = scrubBootstrapValue(child);
|
|
868
|
+
}
|
|
869
|
+
return cleaned;
|
|
870
|
+
}
|
|
871
|
+
|
|
802
872
|
function resolveBootstrapWorkspace(repoNames) {
|
|
803
873
|
const resolved = resolveConfiguredRepos(repoNames);
|
|
804
874
|
if (!resolved.root || !Array.isArray(resolved.repos) || !resolved.repos.length) {
|
|
@@ -826,10 +896,10 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
826
896
|
clearYamlDirectory(storiesDir);
|
|
827
897
|
clearYamlDirectory(missionsDir);
|
|
828
898
|
|
|
829
|
-
const phases = Array.isArray(snapshot.phases) ? snapshot.phases : [];
|
|
830
|
-
const epics = Array.isArray(snapshot.epics) ? snapshot.epics : [];
|
|
831
|
-
const stories = Array.isArray(snapshot.stories) ? snapshot.stories : [];
|
|
832
|
-
const missions = Array.isArray(snapshot.missions) ? snapshot.missions : [];
|
|
899
|
+
const phases = Array.isArray(snapshot.phases) ? snapshot.phases.map((item) => scrubBootstrapValue(item)) : [];
|
|
900
|
+
const epics = Array.isArray(snapshot.epics) ? snapshot.epics.map((item) => scrubBootstrapValue(item)) : [];
|
|
901
|
+
const stories = Array.isArray(snapshot.stories) ? snapshot.stories.map((item) => scrubBootstrapValue(item)) : [];
|
|
902
|
+
const missions = Array.isArray(snapshot.missions) ? snapshot.missions.map((item) => scrubBootstrapValue(item)) : [];
|
|
833
903
|
|
|
834
904
|
phases.forEach((phase, index) => {
|
|
835
905
|
const phaseId = stableItemId(phase, ["phase_id", "id"], `P${String(index + 1).padStart(3, "0")}`);
|
|
@@ -844,19 +914,19 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
844
914
|
writeYamlFile(path.join(storiesDir, `${storyId}.yml`), story);
|
|
845
915
|
});
|
|
846
916
|
missions.forEach((mission, index) => {
|
|
847
|
-
const missionId = stableItemId(mission, ["mission_id", "id"
|
|
917
|
+
const missionId = stableItemId(mission, ["mission_id", "id"], `M${String(index + 1).padStart(3, "0")}`);
|
|
848
918
|
writeYamlFile(path.join(missionsDir, `${missionId}.yml`), mission);
|
|
849
919
|
});
|
|
850
920
|
|
|
851
921
|
if (snapshot.project && typeof snapshot.project === "object") {
|
|
852
|
-
writeYamlFile(path.join(dataRoot, "project.yml"), snapshot.project);
|
|
922
|
+
writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
|
|
853
923
|
}
|
|
854
924
|
|
|
855
925
|
const manifest = {
|
|
856
926
|
schema_version: snapshot.schema_version || 1,
|
|
857
927
|
generated_at: snapshot.generated_at || null,
|
|
858
928
|
snapshot_hash: snapshot.snapshot_hash || null,
|
|
859
|
-
project: snapshot.project
|
|
929
|
+
project: snapshot.project ? scrubBootstrapValue(snapshot.project) : null,
|
|
860
930
|
repo_names: Array.isArray(snapshot.repo_names) ? snapshot.repo_names : [],
|
|
861
931
|
counts: {
|
|
862
932
|
phases: phases.length,
|
|
@@ -874,6 +944,66 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
874
944
|
};
|
|
875
945
|
}
|
|
876
946
|
|
|
947
|
+
function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
948
|
+
const targetRoot = outputDir
|
|
949
|
+
? path.resolve(process.cwd(), String(outputDir))
|
|
950
|
+
: path.join(wrapperRoot, "MyteCommandCenter");
|
|
951
|
+
const dataRoot = path.join(targetRoot, "data");
|
|
952
|
+
const missionsDir = path.join(dataRoot, "missions");
|
|
953
|
+
const qaqcRoot = path.join(dataRoot, "qaqc");
|
|
954
|
+
const activeMissionsDir = path.join(qaqcRoot, "active-missions");
|
|
955
|
+
const manifestPath = path.join(qaqcRoot, "manifest.json");
|
|
956
|
+
|
|
957
|
+
ensureDir(dataRoot);
|
|
958
|
+
ensureDir(missionsDir);
|
|
959
|
+
ensureDir(qaqcRoot);
|
|
960
|
+
const previousManifest = readJsonFile(manifestPath);
|
|
961
|
+
const previousMissionIds = Array.isArray(previousManifest?.active_mission_ids)
|
|
962
|
+
? previousManifest.active_mission_ids.map((item) => String(item).trim()).filter(Boolean)
|
|
963
|
+
: [];
|
|
964
|
+
clearYamlDirectory(activeMissionsDir);
|
|
965
|
+
|
|
966
|
+
const missions = Array.isArray(snapshot.missions) ? snapshot.missions.map((item) => scrubBootstrapValue(item)) : [];
|
|
967
|
+
const currentMissionIds = [];
|
|
968
|
+
|
|
969
|
+
if (snapshot.project && typeof snapshot.project === "object") {
|
|
970
|
+
writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
missions.forEach((mission, index) => {
|
|
974
|
+
const missionId = stableItemId(mission, ["mission_id", "id"], `M${String(index + 1).padStart(3, "0")}`);
|
|
975
|
+
currentMissionIds.push(missionId);
|
|
976
|
+
writeYamlFile(path.join(missionsDir, `${missionId}.yml`), mission);
|
|
977
|
+
writeYamlFile(path.join(activeMissionsDir, `${missionId}.yml`), mission);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
const currentMissionIdSet = new Set(currentMissionIds);
|
|
981
|
+
previousMissionIds.forEach((missionId) => {
|
|
982
|
+
if (!missionId || currentMissionIdSet.has(missionId)) return;
|
|
983
|
+
fs.rmSync(path.join(missionsDir, `${missionId}.yml`), { force: true });
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
const manifest = {
|
|
987
|
+
schema_version: snapshot.schema_version || 1,
|
|
988
|
+
generated_at: snapshot.generated_at || null,
|
|
989
|
+
snapshot_hash: snapshot.snapshot_hash || null,
|
|
990
|
+
project: snapshot.project ? scrubBootstrapValue(snapshot.project) : null,
|
|
991
|
+
repo_names: Array.isArray(snapshot.repo_names) ? snapshot.repo_names : [],
|
|
992
|
+
active_mission_ids: currentMissionIds,
|
|
993
|
+
counts: snapshot.counts && typeof snapshot.counts === "object" ? scrubBootstrapValue(snapshot.counts) : {
|
|
994
|
+
active_missions: missions.length,
|
|
995
|
+
},
|
|
996
|
+
};
|
|
997
|
+
writeJsonFile(manifestPath, manifest);
|
|
998
|
+
writeJsonFile(path.join(qaqcRoot, "latest-batch.json"), snapshot.latest_batch ? scrubBootstrapValue(snapshot.latest_batch) : null);
|
|
999
|
+
|
|
1000
|
+
return {
|
|
1001
|
+
targetRoot,
|
|
1002
|
+
dataRoot,
|
|
1003
|
+
manifest,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
877
1007
|
async function runCreatePrd(args) {
|
|
878
1008
|
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
879
1009
|
if (!key) {
|
|
@@ -1126,6 +1256,97 @@ async function runBootstrap(args) {
|
|
|
1126
1256
|
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
1127
1257
|
}
|
|
1128
1258
|
|
|
1259
|
+
async function runSyncQaqc(args) {
|
|
1260
|
+
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
1261
|
+
if (!key) {
|
|
1262
|
+
console.error("Missing MYTE_API_KEY (project key) in environment/.env");
|
|
1263
|
+
process.exit(1);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const timeoutRaw = args["timeout-ms"] || args.timeoutMs || args.timeout_ms;
|
|
1267
|
+
const timeoutParsed = timeoutRaw !== undefined ? Number(timeoutRaw) : 300_000;
|
|
1268
|
+
const timeoutMs = Number.isFinite(timeoutParsed) ? timeoutParsed : 300_000;
|
|
1269
|
+
|
|
1270
|
+
const baseRaw = args["base-url"] || args.baseUrl || args.base_url || process.env.MYTE_API_BASE || DEFAULT_API_BASE;
|
|
1271
|
+
const apiBase = normalizeApiBase(baseRaw);
|
|
1272
|
+
|
|
1273
|
+
let snapshot;
|
|
1274
|
+
try {
|
|
1275
|
+
snapshot = await fetchQaqcSyncSnapshot({ apiBase, key, timeoutMs });
|
|
1276
|
+
} catch (err) {
|
|
1277
|
+
console.error("Failed to fetch QAQC sync snapshot:", err?.message || err);
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
if (args["print-context"] || args.printContext) {
|
|
1282
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
let resolved;
|
|
1287
|
+
try {
|
|
1288
|
+
resolved = resolveBootstrapWorkspace(snapshot.repo_names || []);
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
console.error(err?.message || err);
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const wrapperRoot = resolved.root;
|
|
1295
|
+
const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
|
|
1296
|
+
const dryRun = Boolean(args["dry-run"] || args.dryRun);
|
|
1297
|
+
const summary = {
|
|
1298
|
+
api_base: apiBase,
|
|
1299
|
+
project_id: snapshot?.project?.id || null,
|
|
1300
|
+
wrapper_root: wrapperRoot,
|
|
1301
|
+
output_root: outputDir ? path.resolve(process.cwd(), String(outputDir)) : path.join(wrapperRoot, "MyteCommandCenter"),
|
|
1302
|
+
repo_names: Array.isArray(snapshot.repo_names) ? snapshot.repo_names : [],
|
|
1303
|
+
local: {
|
|
1304
|
+
mode: resolved.mode,
|
|
1305
|
+
found: (resolved.repos || []).map((repo) => repo.name),
|
|
1306
|
+
missing: resolved.missing || [],
|
|
1307
|
+
},
|
|
1308
|
+
counts: snapshot.counts && typeof snapshot.counts === "object" ? snapshot.counts : {
|
|
1309
|
+
active_missions: Array.isArray(snapshot.missions) ? snapshot.missions.length : 0,
|
|
1310
|
+
},
|
|
1311
|
+
snapshot_hash: snapshot.snapshot_hash || null,
|
|
1312
|
+
generated_at: snapshot.generated_at || null,
|
|
1313
|
+
dry_run: dryRun,
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
if (dryRun) {
|
|
1317
|
+
if (args.json) {
|
|
1318
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
1319
|
+
} else {
|
|
1320
|
+
console.log(`Project: ${summary.project_id || "(unknown)"}`);
|
|
1321
|
+
console.log(`Wrapper root: ${summary.wrapper_root}`);
|
|
1322
|
+
console.log(`Output root: ${summary.output_root}`);
|
|
1323
|
+
console.log(`Configured repos: ${summary.repo_names.join(", ") || "(none)"}`);
|
|
1324
|
+
console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
|
|
1325
|
+
if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
|
|
1326
|
+
console.log(`Counts: active_missions=${summary.counts.active_missions || 0}, todo=${summary.counts.todo || 0}, in_progress=${summary.counts.in_progress || 0}, with_failures=${summary.counts.with_failures || 0}`);
|
|
1327
|
+
console.log("Dry run only - no files written.");
|
|
1328
|
+
}
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
const writeResult = writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir });
|
|
1333
|
+
summary.data_root = writeResult.dataRoot;
|
|
1334
|
+
|
|
1335
|
+
if (args.json) {
|
|
1336
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
console.log(`Project: ${summary.project_id || "(unknown)"}`);
|
|
1341
|
+
console.log(`Wrapper root: ${summary.wrapper_root}`);
|
|
1342
|
+
console.log(`Output root: ${summary.output_root}`);
|
|
1343
|
+
console.log(`Configured repos: ${summary.repo_names.join(", ") || "(none)"}`);
|
|
1344
|
+
console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
|
|
1345
|
+
if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
|
|
1346
|
+
console.log(`Wrote QAQC: active_missions=${summary.counts.active_missions || 0}, todo=${summary.counts.todo || 0}, in_progress=${summary.counts.in_progress || 0}, with_failures=${summary.counts.with_failures || 0}`);
|
|
1347
|
+
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1129
1350
|
async function runQuery(args) {
|
|
1130
1351
|
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
1131
1352
|
if (!key) {
|
|
@@ -1264,6 +1485,11 @@ async function main() {
|
|
|
1264
1485
|
return;
|
|
1265
1486
|
}
|
|
1266
1487
|
|
|
1488
|
+
if (command === "sync-qaqc" || command === "qaqc-sync") {
|
|
1489
|
+
await runSyncQaqc(args);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1267
1493
|
if (command === "chat") {
|
|
1268
1494
|
await runChat(args);
|
|
1269
1495
|
return;
|