@testchimp/cli 0.1.32 → 0.1.34
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/dist/chimphands/run.d.ts +11 -0
- package/dist/chimphands/run.js +266 -0
- package/dist/cli/program.js +29 -0
- package/dist/core/schemas.d.ts +46 -0
- package/dist/core/schemas.js +11 -0
- package/dist/core/tools.js +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChimpHands GitHub Actions bridge: bootstrap → OpenCode → inbound SSE turns.
|
|
3
|
+
* Relies on TESTCHIMP_API_KEY (+ optional TESTCHIMP_BACKEND_URL; defaults to prod).
|
|
4
|
+
* Does not write mcp.json — CLI/skill use process env.
|
|
5
|
+
*/
|
|
6
|
+
type RunOptions = {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
prompt?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function runChimphands(opts: RunOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChimpHands GitHub Actions bridge: bootstrap → OpenCode → inbound SSE turns.
|
|
3
|
+
* Relies on TESTCHIMP_API_KEY (+ optional TESTCHIMP_BACKEND_URL; defaults to prod).
|
|
4
|
+
* Does not write mcp.json — CLI/skill use process env.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import http from "node:http";
|
|
9
|
+
import https from "node:https";
|
|
10
|
+
import { URL } from "node:url";
|
|
11
|
+
import { getBackendUrl, requireApiKey } from "../core/client.js";
|
|
12
|
+
const ROLE_ASSISTANT = "CHIMPHANDS_MESSAGE_ROLE_ASSISTANT";
|
|
13
|
+
const ROLE_TOOL = "CHIMPHANDS_MESSAGE_ROLE_TOOL";
|
|
14
|
+
const ROLE_STATUS = "CHIMPHANDS_MESSAGE_ROLE_STATUS";
|
|
15
|
+
const STATUS_RUNNING = "CHIMPHANDS_SESSION_STATUS_RUNNING";
|
|
16
|
+
const STATUS_WAITING_USER = "CHIMPHANDS_SESSION_STATUS_WAITING_USER";
|
|
17
|
+
const STATUS_IDLE = "CHIMPHANDS_SESSION_STATUS_IDLE";
|
|
18
|
+
const STATUS_FAILED = "CHIMPHANDS_SESSION_STATUS_FAILED";
|
|
19
|
+
function apiHeaders(apiKey) {
|
|
20
|
+
return {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
"TestChimp-Api-Key": apiKey,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function postJson(backend, apiKey, path, body) {
|
|
26
|
+
const res = await fetch(`${backend}${path}`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: apiHeaders(apiKey),
|
|
29
|
+
body: JSON.stringify(body ?? {}),
|
|
30
|
+
});
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`ChimpHands API ${res.status} ${path}: ${text}`);
|
|
34
|
+
}
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
function postJsonFireAndForget(backend, apiKey, path, body) {
|
|
38
|
+
void postJson(backend, apiKey, path, body).catch(() => {
|
|
39
|
+
/* best-effort agent telemetry */
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function writeOpencodeConfig(backend, apiKey, boot) {
|
|
43
|
+
const llmBase = (boot.llm_base_url || `${backend}/v1`).replace(/\/$/, "");
|
|
44
|
+
const llmKey = apiKey || boot.llm_api_key || "";
|
|
45
|
+
const llmModel = boot.llm_model || "gpt-4o-mini";
|
|
46
|
+
writeFileSync("opencode.json", JSON.stringify({
|
|
47
|
+
model: llmModel,
|
|
48
|
+
provider: {
|
|
49
|
+
openai: {
|
|
50
|
+
apiKey: llmKey,
|
|
51
|
+
baseURL: llmBase,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}, null, 2));
|
|
55
|
+
}
|
|
56
|
+
function runOpencode(prompt, childEnv, postEvent) {
|
|
57
|
+
const help = (() => {
|
|
58
|
+
try {
|
|
59
|
+
return execFileSync("opencode", ["run", "--help"], { encoding: "utf8", env: childEnv });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
const useJson = help.includes("--format");
|
|
66
|
+
if (useJson) {
|
|
67
|
+
const child = spawn("opencode", ["run", prompt, "--format", "json"], {
|
|
68
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
env: childEnv,
|
|
70
|
+
});
|
|
71
|
+
let err = "";
|
|
72
|
+
child.stderr.on("data", (d) => {
|
|
73
|
+
err += d.toString();
|
|
74
|
+
});
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
let buf = "";
|
|
77
|
+
child.stdout.on("data", (chunk) => {
|
|
78
|
+
buf += chunk.toString();
|
|
79
|
+
const lines = buf.split("\n");
|
|
80
|
+
buf = lines.pop() || "";
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
if (!line.trim())
|
|
83
|
+
continue;
|
|
84
|
+
let content = line;
|
|
85
|
+
let role = ROLE_ASSISTANT;
|
|
86
|
+
try {
|
|
87
|
+
const ev = JSON.parse(line);
|
|
88
|
+
content = ev.content || ev.message || ev.text || JSON.stringify(ev);
|
|
89
|
+
if (ev.type === "tool" || ev.role === "tool")
|
|
90
|
+
role = ROLE_TOOL;
|
|
91
|
+
if (ev.type === "status")
|
|
92
|
+
role = ROLE_STATUS;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* plain line */
|
|
96
|
+
}
|
|
97
|
+
postEvent(role, content);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
child.on("close", (code) => {
|
|
101
|
+
if (buf.trim())
|
|
102
|
+
postEvent(ROLE_ASSISTANT, buf.trim());
|
|
103
|
+
resolve({ code: code == null ? 1 : code, err });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const out = execFileSync("opencode", ["run", prompt], {
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
111
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
112
|
+
env: childEnv,
|
|
113
|
+
});
|
|
114
|
+
if (out)
|
|
115
|
+
postEvent(ROLE_ASSISTANT, out);
|
|
116
|
+
return Promise.resolve({ code: 0, err: "" });
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
const errObj = e;
|
|
120
|
+
const err = (errObj.stderr && errObj.stderr.toString()) || errObj.message || "opencode failed";
|
|
121
|
+
return Promise.resolve({ code: errObj.status || 1, err });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function connectInbound(backend, apiKey, sessionId, onUserMessage, onIdle, onClosed) {
|
|
125
|
+
const url = new URL(`${backend}/api/chimphands/sessions/${encodeURIComponent(sessionId)}/inbound`);
|
|
126
|
+
const lib = url.protocol === "https:" ? https : http;
|
|
127
|
+
const req = lib.request({
|
|
128
|
+
hostname: url.hostname,
|
|
129
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
130
|
+
path: url.pathname + url.search,
|
|
131
|
+
method: "GET",
|
|
132
|
+
headers: {
|
|
133
|
+
"TestChimp-Api-Key": apiKey,
|
|
134
|
+
Accept: "text/event-stream",
|
|
135
|
+
"Cache-Control": "no-cache",
|
|
136
|
+
},
|
|
137
|
+
}, (res) => {
|
|
138
|
+
let buf = "";
|
|
139
|
+
let eventName = "message";
|
|
140
|
+
res.on("data", (chunk) => {
|
|
141
|
+
buf += chunk.toString();
|
|
142
|
+
const parts = buf.split("\n");
|
|
143
|
+
buf = parts.pop() || "";
|
|
144
|
+
for (const line of parts) {
|
|
145
|
+
if (line.startsWith("event:")) {
|
|
146
|
+
eventName = line.slice(6).trim() || "message";
|
|
147
|
+
}
|
|
148
|
+
else if (line.startsWith("data:")) {
|
|
149
|
+
const data = line.slice(5).trim();
|
|
150
|
+
if (eventName === "idle") {
|
|
151
|
+
onIdle();
|
|
152
|
+
}
|
|
153
|
+
else if (eventName === "user_message" || eventName === "message") {
|
|
154
|
+
try {
|
|
155
|
+
const msg = JSON.parse(data);
|
|
156
|
+
const content = msg.content || "";
|
|
157
|
+
if (content)
|
|
158
|
+
onUserMessage(content);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* ignore */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
eventName = "message";
|
|
165
|
+
}
|
|
166
|
+
else if (line === "") {
|
|
167
|
+
eventName = "message";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
res.on("end", () => onClosed());
|
|
172
|
+
});
|
|
173
|
+
req.on("error", () => onClosed());
|
|
174
|
+
req.end();
|
|
175
|
+
}
|
|
176
|
+
export async function runChimphands(opts) {
|
|
177
|
+
const apiKey = requireApiKey();
|
|
178
|
+
const backend = getBackendUrl();
|
|
179
|
+
// Ensure child processes see the resolved backend (prod default when unset).
|
|
180
|
+
process.env.TESTCHIMP_BACKEND_URL = backend;
|
|
181
|
+
const sessionId = (opts.sessionId || process.env.SESSION_ID || "").trim();
|
|
182
|
+
if (!sessionId) {
|
|
183
|
+
throw new Error("session_id is required (pass --session-id or SESSION_ID)");
|
|
184
|
+
}
|
|
185
|
+
const promptInput = (opts.prompt ?? process.env.PROMPT ?? "").trim();
|
|
186
|
+
const bootText = await postJson(backend, apiKey, "/api/chimphands/bootstrap", {
|
|
187
|
+
session_id: sessionId,
|
|
188
|
+
});
|
|
189
|
+
const boot = JSON.parse(bootText);
|
|
190
|
+
const userId = boot.chimphands_service_account_user_id || "";
|
|
191
|
+
if (userId) {
|
|
192
|
+
process.env.TESTCHIMP_USER_ID = userId;
|
|
193
|
+
}
|
|
194
|
+
mkdirSync(".opencode", { recursive: true });
|
|
195
|
+
writeOpencodeConfig(backend, apiKey, boot);
|
|
196
|
+
const idleMs = (Number(boot.idle_timeout_seconds) || 600) * 1000;
|
|
197
|
+
const queue = [];
|
|
198
|
+
let idle = false;
|
|
199
|
+
let closed = false;
|
|
200
|
+
let lastUserActivity = Date.now();
|
|
201
|
+
const postEvent = (role, content, status) => {
|
|
202
|
+
const body = {
|
|
203
|
+
session_id: sessionId,
|
|
204
|
+
role,
|
|
205
|
+
content: String(content || "").slice(0, 20000),
|
|
206
|
+
};
|
|
207
|
+
if (status != null)
|
|
208
|
+
body.status = status;
|
|
209
|
+
postJsonFireAndForget(backend, apiKey, "/api/chimphands/post_agent_event", body);
|
|
210
|
+
};
|
|
211
|
+
const complete = (status, errorMessage) => {
|
|
212
|
+
const body = { session_id: sessionId, status };
|
|
213
|
+
if (errorMessage)
|
|
214
|
+
body.error_message = String(errorMessage).slice(0, 4000);
|
|
215
|
+
postJsonFireAndForget(backend, apiKey, "/api/chimphands/complete_session", body);
|
|
216
|
+
};
|
|
217
|
+
const childEnv = {
|
|
218
|
+
...process.env,
|
|
219
|
+
TESTCHIMP_API_KEY: apiKey,
|
|
220
|
+
TESTCHIMP_BACKEND_URL: backend,
|
|
221
|
+
};
|
|
222
|
+
if (userId)
|
|
223
|
+
childEnv.TESTCHIMP_USER_ID = userId;
|
|
224
|
+
connectInbound(backend, apiKey, sessionId, (content) => {
|
|
225
|
+
queue.push(content);
|
|
226
|
+
lastUserActivity = Date.now();
|
|
227
|
+
}, () => {
|
|
228
|
+
idle = true;
|
|
229
|
+
}, () => {
|
|
230
|
+
closed = true;
|
|
231
|
+
});
|
|
232
|
+
postEvent(ROLE_STATUS, "Agent ready", STATUS_RUNNING);
|
|
233
|
+
let prompt = promptInput || boot.initial_prompt || "";
|
|
234
|
+
if (boot.conversation_summary) {
|
|
235
|
+
prompt = `Conversation so far:\n${boot.conversation_summary}\n\nCurrent task:\n${prompt}`;
|
|
236
|
+
}
|
|
237
|
+
for (const m of boot.pending_user_messages || []) {
|
|
238
|
+
if (m?.content)
|
|
239
|
+
queue.push(m.content);
|
|
240
|
+
}
|
|
241
|
+
const waitForNextPrompt = () => new Promise((resolve) => {
|
|
242
|
+
const tick = () => {
|
|
243
|
+
if (queue.length) {
|
|
244
|
+
resolve(queue.shift());
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (idle || closed || Date.now() - lastUserActivity >= idleMs) {
|
|
248
|
+
resolve(null);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
setTimeout(tick, 500);
|
|
252
|
+
};
|
|
253
|
+
tick();
|
|
254
|
+
});
|
|
255
|
+
while (prompt) {
|
|
256
|
+
const result = await runOpencode(prompt, childEnv, postEvent);
|
|
257
|
+
if (result.code !== 0) {
|
|
258
|
+
complete(STATUS_FAILED, result.err || "opencode failed");
|
|
259
|
+
process.exitCode = result.code || 1;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
postEvent(ROLE_STATUS, "Waiting for user input", STATUS_WAITING_USER);
|
|
263
|
+
prompt = (await waitForNextPrompt()) || "";
|
|
264
|
+
}
|
|
265
|
+
complete(STATUS_IDLE);
|
|
266
|
+
}
|
package/dist/cli/program.js
CHANGED
|
@@ -1048,6 +1048,15 @@ export function buildCliProgram() {
|
|
|
1048
1048
|
const out = await runTool("mark-semantic-tests-distinct", merged, { postMcp });
|
|
1049
1049
|
console.log(out);
|
|
1050
1050
|
});
|
|
1051
|
+
program
|
|
1052
|
+
.command("mark-tests-for-review")
|
|
1053
|
+
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "mark-tests-for-review").description)
|
|
1054
|
+
.addOption(jsonInputOption())
|
|
1055
|
+
.action(async (opts) => {
|
|
1056
|
+
const merged = mergeBodies({}, opts.jsonInput);
|
|
1057
|
+
const out = await runTool("mark-tests-for-review", merged, { postMcp });
|
|
1058
|
+
console.log(out);
|
|
1059
|
+
});
|
|
1051
1060
|
program
|
|
1052
1061
|
.command("list-semantic-nearby")
|
|
1053
1062
|
.description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-semantic-nearby").description)
|
|
@@ -1498,6 +1507,26 @@ export function buildCliProgram() {
|
|
|
1498
1507
|
body.limit = opts.limit;
|
|
1499
1508
|
console.log(await runTool("list-api-operation-interactions", mergeBodies(body, opts.jsonInput), { postMcp }));
|
|
1500
1509
|
});
|
|
1510
|
+
const chimphands = program.command("chimphands").description("ChimpHands GitHub Actions agent bridge");
|
|
1511
|
+
chimphands
|
|
1512
|
+
.command("run")
|
|
1513
|
+
.description("Bootstrap session, configure OpenCode, and run the interactive bridge")
|
|
1514
|
+
.option("--session-id <id>", "ChimpHands session id (or SESSION_ID env)")
|
|
1515
|
+
.option("--prompt <text>", "Initial prompt (or PROMPT env)")
|
|
1516
|
+
.action(async (opts) => {
|
|
1517
|
+
const { runChimphands } = await import("../chimphands/run.js");
|
|
1518
|
+
try {
|
|
1519
|
+
await runChimphands({
|
|
1520
|
+
sessionId: String(opts.sessionId || process.env.SESSION_ID || "").trim(),
|
|
1521
|
+
prompt: opts.prompt != null ? String(opts.prompt) : undefined,
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
catch (e) {
|
|
1525
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1526
|
+
console.error(`[testchimp chimphands] ${msg}`);
|
|
1527
|
+
process.exitCode = 1;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1501
1530
|
program.on("--help", () => {
|
|
1502
1531
|
/* default */
|
|
1503
1532
|
});
|
package/dist/core/schemas.d.ts
CHANGED
|
@@ -984,6 +984,52 @@ export declare const markSemanticTestsDistinctInput: z.ZodObject<{
|
|
|
984
984
|
testName: z.ZodString;
|
|
985
985
|
}, z.core.$strip>;
|
|
986
986
|
}, z.core.$strip>;
|
|
987
|
+
export declare const markTestsForReviewInput: z.ZodObject<{
|
|
988
|
+
tests: z.ZodArray<z.ZodObject<{
|
|
989
|
+
test: z.ZodObject<{
|
|
990
|
+
folderPath: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
991
|
+
fileName: z.ZodString;
|
|
992
|
+
testSuite: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
993
|
+
testName: z.ZodString;
|
|
994
|
+
}, z.core.$strip>;
|
|
995
|
+
confidence: z.ZodNumber;
|
|
996
|
+
}, z.core.$strip>>;
|
|
997
|
+
gitCommitSha: z.ZodOptional<z.ZodString>;
|
|
998
|
+
workflowId: z.ZodOptional<z.ZodString>;
|
|
999
|
+
workflowExecutionId: z.ZodOptional<z.ZodString>;
|
|
1000
|
+
policyFile: z.ZodOptional<z.ZodString>;
|
|
1001
|
+
policyVersion: z.ZodOptional<z.ZodString>;
|
|
1002
|
+
gitSha: z.ZodOptional<z.ZodString>;
|
|
1003
|
+
actorType: z.ZodOptional<z.ZodEnum<{
|
|
1004
|
+
LOCAL_AGENT: "LOCAL_AGENT";
|
|
1005
|
+
CLOUD_AGENT: "CLOUD_AGENT";
|
|
1006
|
+
"local-agent": "local-agent";
|
|
1007
|
+
"cloud-agent": "cloud-agent";
|
|
1008
|
+
}>>;
|
|
1009
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
1010
|
+
branchName: z.ZodOptional<z.ZodString>;
|
|
1011
|
+
agentModel: z.ZodOptional<z.ZodString>;
|
|
1012
|
+
skillVersion: z.ZodOptional<z.ZodString>;
|
|
1013
|
+
cliVersion: z.ZodOptional<z.ZodString>;
|
|
1014
|
+
agentTraceability: z.ZodOptional<z.ZodObject<{
|
|
1015
|
+
workflowId: z.ZodOptional<z.ZodString>;
|
|
1016
|
+
workflowExecutionId: z.ZodOptional<z.ZodString>;
|
|
1017
|
+
policyFile: z.ZodOptional<z.ZodString>;
|
|
1018
|
+
policyVersion: z.ZodOptional<z.ZodString>;
|
|
1019
|
+
gitSha: z.ZodOptional<z.ZodString>;
|
|
1020
|
+
actorType: z.ZodOptional<z.ZodEnum<{
|
|
1021
|
+
LOCAL_AGENT: "LOCAL_AGENT";
|
|
1022
|
+
CLOUD_AGENT: "CLOUD_AGENT";
|
|
1023
|
+
"local-agent": "local-agent";
|
|
1024
|
+
"cloud-agent": "cloud-agent";
|
|
1025
|
+
}>>;
|
|
1026
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
1027
|
+
branchName: z.ZodOptional<z.ZodString>;
|
|
1028
|
+
agentModel: z.ZodOptional<z.ZodString>;
|
|
1029
|
+
skillVersion: z.ZodOptional<z.ZodString>;
|
|
1030
|
+
cliVersion: z.ZodOptional<z.ZodString>;
|
|
1031
|
+
}, z.core.$strict>>;
|
|
1032
|
+
}, z.core.$strip>;
|
|
987
1033
|
/** LinkedEntityType names for semantic nearby (embedding-capable). */
|
|
988
1034
|
export declare const semanticNearbyEntityTypeSchema: z.ZodEnum<{
|
|
989
1035
|
STORY: "STORY";
|
package/dist/core/schemas.js
CHANGED
|
@@ -457,6 +457,17 @@ export const markSemanticTestsDistinctInput = z.object({
|
|
|
457
457
|
focusTest: testLocatorSchema,
|
|
458
458
|
distinctTest: testLocatorSchema,
|
|
459
459
|
});
|
|
460
|
+
export const markTestsForReviewInput = z
|
|
461
|
+
.object({
|
|
462
|
+
tests: z
|
|
463
|
+
.array(z.object({
|
|
464
|
+
test: testLocatorSchema,
|
|
465
|
+
confidence: z.number().int().min(0).max(100),
|
|
466
|
+
}))
|
|
467
|
+
.min(1),
|
|
468
|
+
gitCommitSha: z.string().optional(),
|
|
469
|
+
})
|
|
470
|
+
.merge(agentTraceabilityFieldsSchema);
|
|
460
471
|
/** LinkedEntityType names for semantic nearby (embedding-capable). */
|
|
461
472
|
export const semanticNearbyEntityTypeSchema = z.enum([
|
|
462
473
|
"STORY",
|
package/dist/core/tools.js
CHANGED
|
@@ -880,6 +880,39 @@ export const TOOL_DEFINITIONS = [
|
|
|
880
880
|
});
|
|
881
881
|
},
|
|
882
882
|
},
|
|
883
|
+
{
|
|
884
|
+
kebab: "mark-tests-for-review",
|
|
885
|
+
description: "Report existing SmartTests that an agent patched so humans can re-verify. " +
|
|
886
|
+
"Always send per-test confidence 0–100 (higher = less need for human review). " +
|
|
887
|
+
"Do not read project config. Call only from fix-test-execution after test-incorrect patches " +
|
|
888
|
+
"(never from run-qa / create-tests; never for product-broken cases). " +
|
|
889
|
+
"Optional agentTraceability / workflowExecutionId / gitCommitSha / branchName.",
|
|
890
|
+
inputSchema: S.markTestsForReviewInput,
|
|
891
|
+
execute: async (args, { postMcp }) => {
|
|
892
|
+
const a = args;
|
|
893
|
+
const nested = a.agentTraceability && typeof a.agentTraceability === "object"
|
|
894
|
+
? a.agentTraceability
|
|
895
|
+
: undefined;
|
|
896
|
+
const nestedStr = (key) => {
|
|
897
|
+
const v = nested?.[key];
|
|
898
|
+
return typeof v === "string" && v.trim() ? v : undefined;
|
|
899
|
+
};
|
|
900
|
+
const body = { tests: a.tests };
|
|
901
|
+
const branchName = a.branchName || nestedStr("branchName");
|
|
902
|
+
if (branchName)
|
|
903
|
+
body.branchName = branchName;
|
|
904
|
+
const gitCommitSha = a.gitCommitSha ?? a.gitSha ?? nestedStr("gitSha");
|
|
905
|
+
if (gitCommitSha)
|
|
906
|
+
body.gitCommitSha = gitCommitSha;
|
|
907
|
+
const workflowExecutionId = a.workflowExecutionId || nestedStr("workflowExecutionId");
|
|
908
|
+
if (workflowExecutionId)
|
|
909
|
+
body.workflowExecutionId = workflowExecutionId;
|
|
910
|
+
const trace = buildAgentTraceabilityPayload(a);
|
|
911
|
+
if (trace)
|
|
912
|
+
body.agentTraceability = trace;
|
|
913
|
+
return postMcp("/api/mcp/mark_tests_for_review", body);
|
|
914
|
+
},
|
|
915
|
+
},
|
|
883
916
|
{
|
|
884
917
|
kebab: "list-semantic-nearby",
|
|
885
918
|
description: "List semantically nearby entities across types (Story/Scenario/Test/Issue/Event). " +
|
package/package.json
CHANGED