@mytegroupinc/myte-core 0.0.53 → 0.0.55

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.
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const { randomUUID } = require("node:crypto");
8
+ const { spawn, spawnSync } = require("node:child_process");
9
+
10
+ const ROOT = path.resolve(__dirname, "..");
11
+ const CLI = path.join(ROOT, "cli.js");
12
+ const ROUTES = [
13
+ "POST /query", "GET /query/<job_id>", "GET /config", "GET /bootstrap",
14
+ "GET /qaqc-sync", "POST /run-qaqc", "GET /run-qaqc/<batch_id>",
15
+ "POST /mission-status-update", "POST /mission-archive", "GET /feedback-sync",
16
+ "GET /feedback/<feedback_id>", "POST /feedback/<feedback_id>/refinement/validate",
17
+ "POST /feedback/<feedback_id>/refinement/requests",
18
+ "POST /feedback-review-requests/<request_id>/revise",
19
+ "POST /feedback/<feedback_id>/refinement/apply",
20
+ "GET /feedback/<feedback_id>/refinement/history", "GET /feedback-review-requests",
21
+ "GET /feedback-review-requests/<request_id>",
22
+ "POST /feedback-review-requests/<request_id>/review",
23
+ "POST /feedback-review-requests/batch-review",
24
+ "POST /feedback-review-requests/<request_id>/request-changes",
25
+ "POST /feedback-review-requests/<request_id>/cancel",
26
+ "POST /feedback/<feedback_id>/board-move", "POST /feedback/batch-board-move",
27
+ "POST /feedback/<feedback_id>/comments", "GET /feedback/<feedback_id>/events",
28
+ "POST /feedback/<feedback_id>/events/<event_id>/undo",
29
+ "GET /feedback/<feedback_id>/prd/versions",
30
+ "GET /feedback/<feedback_id>/prd/versions/<version_id>/diff",
31
+ "POST /project-comment", "POST /update-owner", "POST /client-update-drafts",
32
+ "POST /create-prd", "POST /create-prds", "GET /suggestions", "POST /suggestions",
33
+ "POST /suggestions/revise", "POST /suggestions/review",
34
+ ];
35
+
36
+ const COVERAGE = {
37
+ read: ROUTES.filter((route) => route.startsWith("GET ") || route.startsWith("POST /query")),
38
+ mission: ROUTES.filter((route) => /mission|qaqc|suggestion/.test(route)),
39
+ feedback: ROUTES.filter((route) => /feedback|create-prd/.test(route)),
40
+ communications: ROUTES.filter((route) => /project-comment|update-owner|client-update/.test(route)),
41
+ };
42
+
43
+ function parseArgs(argv) {
44
+ const args = {};
45
+ for (let index = 0; index < argv.length; index += 1) {
46
+ const token = argv[index];
47
+ if (!token.startsWith("--")) continue;
48
+ const next = argv[index + 1];
49
+ if (!next || next.startsWith("--")) args[token.slice(2)] = true;
50
+ else { args[token.slice(2)] = next; index += 1; }
51
+ }
52
+ return args;
53
+ }
54
+
55
+ function routeCoverage() {
56
+ const accounted = new Set(Object.values(COVERAGE).flat());
57
+ const missing = ROUTES.filter((route) => !accounted.has(route));
58
+ if (ROUTES.length !== 38 || new Set(ROUTES).size !== 38 || missing.length) {
59
+ throw new Error(`Route manifest is incomplete: ${missing.join(", ") || "count/duplicate mismatch"}`);
60
+ }
61
+ return { implemented_project_key_routes: 38, accounted_routes: accounted.size, missing };
62
+ }
63
+
64
+ function runNode(script, args, cwd) {
65
+ const result = spawnSync(process.execPath, [script, ...args], {
66
+ cwd, env: process.env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 1_800_000,
67
+ });
68
+ let output = null;
69
+ try { output = JSON.parse(String(result.stdout || "").trim() || "null"); } catch {}
70
+ return {
71
+ command: path.basename(script), ok: result.status === 0,
72
+ exit_code: result.status, output,
73
+ error: result.status === 0 ? null : String(result.stderr || result.stdout || "failed").trim().slice(0, 2000),
74
+ };
75
+ }
76
+
77
+ function runCli(args, cwd) {
78
+ return runNode(CLI, args, cwd);
79
+ }
80
+
81
+ function runNodeAsync(script, args, cwd) {
82
+ return new Promise((resolve) => {
83
+ const child = spawn(process.execPath, [script, ...args], { cwd, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
84
+ let stdout = "";
85
+ let stderr = "";
86
+ child.stdout.on("data", (chunk) => { stdout += chunk; });
87
+ child.stderr.on("data", (chunk) => { stderr += chunk; });
88
+ child.on("close", (code) => {
89
+ let output = null;
90
+ try { output = JSON.parse(stdout.trim() || "null"); } catch {}
91
+ resolve({ ok: code === 0, exit_code: code, output, error: code === 0 ? null : (stderr || stdout).trim().slice(0, 2000) });
92
+ });
93
+ });
94
+ }
95
+
96
+ async function assertExpectedProject(baseUrl, expectedProject) {
97
+ const key = process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY;
98
+ if (!key) throw new Error("Missing MYTE_API_KEY or MYTE_PROJECT_API_KEY.");
99
+ const response = await fetch(`${String(baseUrl).replace(/\/+$/, "")}/project-assistant/bootstrap`, {
100
+ headers: { Authorization: `Bearer ${key}` },
101
+ });
102
+ const body = await response.json();
103
+ const title = String(body?.data?.project?.title || "").trim();
104
+ if (!response.ok || body?.status !== "success") throw new Error("Project bootstrap preflight failed.");
105
+ if (title.toLowerCase() !== String(expectedProject).trim().toLowerCase()) {
106
+ throw new Error(`Live target mismatch: expected ${expectedProject}, received ${title || "unknown"}.`);
107
+ }
108
+ return { project_id: body.data.project.id, title };
109
+ }
110
+
111
+ async function main() {
112
+ const args = parseArgs(process.argv.slice(2));
113
+ const coverage = routeCoverage();
114
+ if (!args["confirm-live"]) {
115
+ console.log(JSON.stringify({
116
+ ok: true, dry_run: true, network_requests: 0, live_mutations: 0, coverage,
117
+ stages: ["read", "mission", "feedback", "communications", "concurrent-idempotency", "exact-id-cleanup"],
118
+ required_live_flags: ["--confirm-live", "--expected-project GaahField", "--include-communications"],
119
+ owner_email_requires: "--include-owner-email",
120
+ }, null, 2));
121
+ return;
122
+ }
123
+ const expectedProject = String(args["expected-project"] || "").trim();
124
+ if (expectedProject !== "GaahField") throw new Error("Live certification is pinned to --expected-project GaahField.");
125
+ if (!args["include-communications"]) throw new Error("Full live certification requires --include-communications.");
126
+ const baseUrl = String(args["base-url"] || process.env.MYTE_API_BASE || "https://api.myte.dev").replace(/\/+$/, "");
127
+ const project = await assertExpectedProject(baseUrl, expectedProject);
128
+ const runId = String(args["run-id"] || randomUUID());
129
+ const workspace = path.resolve(args.workspace || fs.mkdtempSync(path.join(os.tmpdir(), `myte-full-${runId}-`)));
130
+ fs.mkdirSync(workspace, { recursive: true });
131
+ const common = ["--base-url", baseUrl, "--run-id", runId];
132
+ const results = [];
133
+ results.push(runNode(path.join(__dirname, "project-assistant-read-certification.js"), ["--confirm-read", "--include-query", ...common], workspace));
134
+ results.push(runNode(path.join(__dirname, "mission-live-full-harness.js"), ["--confirm-live", ...common], workspace));
135
+ results.push(runNode(path.join(__dirname, "feedback-live-full-harness.js"), ["--confirm-live", ...common], workspace));
136
+
137
+ const team = path.join(workspace, `team-${runId}.md`);
138
+ const client = path.join(workspace, `client-${runId}.md`);
139
+ fs.writeFileSync(team, `Project Assistant certification ${runId}: team update route verified.\n`, "utf8");
140
+ fs.writeFileSync(client, `Project Assistant certification ${runId}: client draft route verified; do not send.\n`, "utf8");
141
+ const teamArgs = ["update-team", `Certification ${runId}`, "--confirm-write", "--approval-artifact", team, "--idempotency-key", `cert-team-${runId}`, "--json", "--base-url", baseUrl];
142
+ const concurrentTeam = await Promise.all(Array.from({ length: 10 }, () => runNodeAsync(CLI, teamArgs, workspace)));
143
+ const teamFingerprints = new Set(
144
+ concurrentTeam.filter((item) => item.ok).map((item) => JSON.stringify(item.output?.data || item.output || null)),
145
+ );
146
+ results.push({
147
+ command: "concurrent-idempotency:update-team",
148
+ ok: concurrentTeam.every((item) => item.ok) && teamFingerprints.size === 1,
149
+ attempts: concurrentTeam.length,
150
+ unique_outcomes: teamFingerprints.size,
151
+ error: concurrentTeam.find((item) => !item.ok)?.error || (teamFingerprints.size === 1 ? null : "Concurrent replays returned different mutation outcomes."),
152
+ });
153
+ results.push(runCli(["update-client", "--subject", `Certification ${runId}`, "--body-file", client, "--confirm-write", "--approval-artifact", client, "--idempotency-key", `cert-client-${runId}`, "--json", "--base-url", baseUrl], workspace));
154
+ if (args["include-owner-email"]) {
155
+ const owner = path.join(workspace, `owner-${runId}.md`);
156
+ fs.writeFileSync(owner, `Project Assistant certification ${runId}: owner delivery route verified.\n`, "utf8");
157
+ results.push(runCli(["update-owner", "--subject", `Certification ${runId}`, "--body-file", owner, "--confirm-write", "--approval-artifact", owner, "--idempotency-key", `cert-owner-${runId}`, "--json", "--base-url", baseUrl], workspace));
158
+ }
159
+ const failed = results.filter((result) => !result.ok);
160
+ const report = { ok: failed.length === 0, run_id: runId, project, coverage, workspace, results, failed_count: failed.length };
161
+ const reportPath = path.join(workspace, `project-assistant-certification-${runId}.json`);
162
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
163
+ console.log(JSON.stringify({ ...report, report_path: reportPath }, null, 2));
164
+ if (failed.length) process.exitCode = 1;
165
+ }
166
+
167
+ if (require.main === module) {
168
+ main().catch((error) => {
169
+ console.error(JSON.stringify({ ok: false, message: error?.message || String(error) }, null, 2));
170
+ process.exit(1);
171
+ });
172
+ } else {
173
+ module.exports = { COVERAGE, ROUTES, routeCoverage };
174
+ }