@topline-build/harness-mcp 0.4.0 → 0.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topline-build/harness-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Topline Build MCP for creating and deploying artifact app drafts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,10 @@
15
15
  "homepage": "https://github.com/topline-build/topline-mcp#readme",
16
16
  "files": [
17
17
  "dist/",
18
- "README.md"
18
+ "docs/generated/",
19
+ "CHANGELOG.md",
20
+ "README.md",
21
+ "scripts/certify-live.mjs"
19
22
  ],
20
23
  "bin": {
21
24
  "topline-mcp": "dist/cli.js"
@@ -32,14 +35,17 @@
32
35
  "scripts": {
33
36
  "build": "npm run clean && tsc -p tsconfig.json",
34
37
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
38
+ "docs:generate": "npm run build && node scripts/generate-docs.mjs",
39
+ "docs:check": "npm run build && node scripts/generate-docs.mjs --check",
35
40
  "mcp:stdio": "node dist/cli.js",
36
- "prepack": "npm run build",
41
+ "prepack": "npm run docs:check",
37
42
  "test": "vitest run",
38
43
  "typecheck": "tsc --noEmit -p tsconfig.json",
39
- "certify:live": "node scripts/certify-live.mjs"
44
+ "certify:live": "node scripts/certify-live.mjs",
45
+ "certify:clients": "node scripts/certify-live.mjs --clients codex,claude,cursor"
40
46
  },
41
47
  "dependencies": {
42
- "@modelcontextprotocol/sdk": "^1.29.0"
48
+ "@modelcontextprotocol/sdk": "1.29.0"
43
49
  },
44
50
  "devDependencies": {
45
51
  "@types/node": "^22.15.3",
@@ -0,0 +1,551 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { createHarnessMcpStdioServer, parseHarnessOptions } from "../dist/index.js";
11
+
12
+ const CLIENTS = ["codex", "claude", "cursor"];
13
+ const EXPECTED_TOOLS = [
14
+ "platform_inspect_artifact_app",
15
+ "platform_deploy_artifact_app",
16
+ "platform_list_build_workspaces",
17
+ "platform_list_artifact_drafts",
18
+ "platform_wait_artifact_deploy",
19
+ "platform_run_artifact_smoke",
20
+ "platform_validate_artifact",
21
+ "platform_plan_artifact_draft_delete"
22
+ ];
23
+ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
24
+ const manifest = JSON.parse(await readFile(new URL("../docs/generated/build-mcp-manifest.json", import.meta.url), "utf8"));
25
+ let args;
26
+
27
+ if (process.argv[2] === "--certification-read-only-server") {
28
+ await runCertificationReadOnlyServer(process.argv.slice(3));
29
+ } else {
30
+ args = parseArgs(process.argv.slice(2));
31
+ const report = args.selfTest ? selfTestReport() : await certify();
32
+ const output = `${JSON.stringify(report, null, 2)}\n`;
33
+ if (args.output) await writeFile(path.resolve(args.output), output, "utf8");
34
+ process.stdout.write(output);
35
+
36
+ if (report.status === "failed") process.exitCode = 1;
37
+ else if (args.requireAll && report.status !== "passed") process.exitCode = 2;
38
+ }
39
+
40
+ async function runCertificationReadOnlyServer(values) {
41
+ delete process.env.ANTHROPIC_API_KEY;
42
+ delete process.env.TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY;
43
+ const opts = parseHarnessOptions(values, withoutManualToken(process.env));
44
+ const server = createHarnessMcpStdioServer(opts, ["platform_inspect_artifact_app"]);
45
+ await server.connect(new StdioServerTransport());
46
+ }
47
+
48
+ async function certify() {
49
+ const workspaceRoot = path.resolve(process.env.TOPLINE_WORKSPACE_ROOT || process.cwd());
50
+ const appDir = process.env.TOPLINE_CERTIFICATION_APP_DIR?.trim();
51
+ const apiUrl = process.env.TOPLINE_API_URL?.trim();
52
+ const token = process.env.TOPLINE_MCP_TOKEN?.trim();
53
+ const prerequisites = {
54
+ apiUrl: apiUrl ? "available" : "missing",
55
+ appDir: appDir ? "available" : "missing",
56
+ credentials: token ? "manual_token" : "missing"
57
+ };
58
+ const clientResults = [];
59
+ for (const name of args.clients) {
60
+ clientResults.push(await probeClient(name, apiUrl, appDir, workspaceRoot));
61
+ }
62
+
63
+ let protocol;
64
+ if (!apiUrl || !appDir) {
65
+ protocol = skipped("missing_topline_url_or_certification_app");
66
+ } else {
67
+ protocol = await certifyProtocol({ apiUrl, token, appDir, workspaceRoot });
68
+ }
69
+
70
+ const checks = [...clientResults.flatMap((entry) => [entry.installation, entry.configuration, entry.protocolToolDiscovery, entry.clientMediatedRead]), protocol];
71
+ const failed = checks.some((entry) => entry.status === "failed");
72
+ const incomplete = checks.some((entry) => entry.status === "skipped");
73
+ return {
74
+ schemaVersion: 1,
75
+ status: failed ? "failed" : incomplete ? "partial" : "passed",
76
+ evidenceMode: "live",
77
+ package: { name: packageJson.name, version: packageJson.version },
78
+ protocol: { transport: manifest.protocol.transport, version: manifest.protocol.defaultVersion },
79
+ phase: args.phase,
80
+ auth: {
81
+ preferred: "oauth_browser_approval",
82
+ certificationMode: prerequisites.credentials,
83
+ boundary: "Noninteractive certification accepts only an operator-provided TOPLINE_MCP_TOKEN; normal client setup should use browser-approved OAuth."
84
+ },
85
+ prerequisites,
86
+ clients: clientResults,
87
+ workflow: protocol
88
+ };
89
+ }
90
+
91
+ async function probeClient(name, apiUrl, appDir, workspaceRoot) {
92
+ const command = clientCommand(name);
93
+ const version = run(command, ["--version"], process.cwd());
94
+ if (version.missing) {
95
+ return {
96
+ client: name,
97
+ installation: skipped("client_cli_not_installed"),
98
+ configuration: skipped("client_cli_not_installed"),
99
+ protocolToolDiscovery: skipped("client_cli_not_installed"),
100
+ clientMediatedRead: skipped("client_cli_not_installed")
101
+ };
102
+ }
103
+ if (!version.ok) {
104
+ return {
105
+ client: name,
106
+ installation: failed("client_version_command_failed", version.exitCode),
107
+ configuration: skipped("installation_failed"),
108
+ protocolToolDiscovery: skipped("installation_failed"),
109
+ clientMediatedRead: skipped("installation_failed")
110
+ };
111
+ }
112
+ if (!apiUrl) {
113
+ return {
114
+ client: name,
115
+ installation: passed({ versionDetected: true }),
116
+ configuration: skipped("topline_api_url_missing"),
117
+ protocolToolDiscovery: skipped("topline_api_url_missing"),
118
+ clientMediatedRead: skipped("topline_api_url_missing")
119
+ };
120
+ }
121
+
122
+ const scratch = await mkdtemp(path.join(os.tmpdir(), `topline-${name}-cert-`));
123
+ try {
124
+ const cliPath = path.resolve("dist/cli.js");
125
+ const configResult = await clientConfigurationProbe(name, command, scratch, cliPath, apiUrl, workspaceRoot);
126
+ const direct = await directToolDiscovery(name, apiUrl, workspaceRoot);
127
+ const mediated = args.agentMediated
128
+ ? await agentMediatedRead(name, command, scratch, apiUrl, appDir, workspaceRoot)
129
+ : skipped("agent_mediated_probe_not_requested");
130
+ return {
131
+ client: name,
132
+ installation: passed({ versionDetected: true }),
133
+ configuration: configResult,
134
+ protocolToolDiscovery: direct,
135
+ clientMediatedRead: mediated
136
+ };
137
+ } finally {
138
+ await rm(scratch, { recursive: true, force: true });
139
+ }
140
+ }
141
+
142
+ async function clientConfigurationProbe(name, command, scratch, cliPath, apiUrl, workspaceRoot) {
143
+ const server = { command: process.execPath, args: [cliPath, "--url", apiUrl, "--workspace-root", workspaceRoot] };
144
+ if (name === "codex") {
145
+ const tomlServer = 'mcp_servers."topline-build"';
146
+ const result = run(command, [
147
+ "mcp", "list", "--json",
148
+ "-c", `${tomlServer}.command=${JSON.stringify(server.command)}`,
149
+ "-c", `${tomlServer}.args=${JSON.stringify(server.args)}`
150
+ ], scratch);
151
+ if (!result.ok) return failed("client_ephemeral_config_rejected", result.exitCode);
152
+ try {
153
+ JSON.parse(result.stdout);
154
+ return passed({ scope: "ephemeral" });
155
+ } catch {
156
+ return failed("client_config_output_invalid", result.exitCode);
157
+ }
158
+ }
159
+
160
+ const configDir = path.join(scratch, name === "cursor" ? ".cursor" : ".topline-cert");
161
+ await mkdir(configDir, { recursive: true });
162
+ const configPath = path.join(configDir, "mcp.json");
163
+ await writeFile(configPath, JSON.stringify({ mcpServers: { "topline-build": server } }, null, 2), "utf8");
164
+ if (name === "claude") {
165
+ const added = run(command, ["mcp", "add-json", "--scope", "project", "topline-build", JSON.stringify({ type: "stdio", ...server })], scratch);
166
+ if (!added.ok) return failed("client_ephemeral_config_rejected", added.exitCode);
167
+ const result = run(command, ["mcp", "get", "topline-build"], scratch);
168
+ return result.ok ? passed({ scope: "temporary_project" }) : failed("client_ephemeral_config_not_listed", result.exitCode);
169
+ }
170
+ const result = run(command, ["mcp", "list"], scratch);
171
+ if (!result.ok) return failed("client_ephemeral_config_rejected", result.exitCode);
172
+ return result.stdout.includes("topline-build")
173
+ ? passed({ scope: "ephemeral", approval: "operator_managed" })
174
+ : skipped("cursor_project_mcp_requires_operator_approval");
175
+ }
176
+
177
+ async function agentMediatedRead(name, command, scratch, apiUrl, appDir, workspaceRoot) {
178
+ if (name !== "claude") return skipped("strict_client_tool_isolation_unavailable");
179
+ if (!appDir) return skipped("certification_app_missing");
180
+ const server = {
181
+ command: process.execPath,
182
+ env: {
183
+ ANTHROPIC_API_KEY: "",
184
+ TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY: ""
185
+ },
186
+ args: [
187
+ fileURLToPath(import.meta.url),
188
+ "--certification-read-only-server",
189
+ "--url", apiUrl,
190
+ "--workspace-root", workspaceRoot,
191
+ "--auth-file", path.join(scratch, "certification-auth.json"),
192
+ "--scopes", "harness:artifact:read"
193
+ ]
194
+ };
195
+ const prompt = `Use only the Topline Build MCP server. Call platform_inspect_artifact_app exactly once with appDir ${JSON.stringify(appDir)}. Do not call shell, file-write, deployment, smoke, or delete tools. If packageReady is true, respond with TOPLINE_CLIENT_READ_CERTIFIED. Otherwise explain only the returned error code.`;
196
+ const configDir = path.join(scratch, ".topline-agent-cert");
197
+ await mkdir(configDir, { recursive: true });
198
+ const configPath = path.join(configDir, "mcp.json");
199
+ await writeFile(configPath, JSON.stringify({ mcpServers: { "topline-build": server } }, null, 2), "utf8");
200
+ const result = run(command, [
201
+ "--mcp-config", configPath, "--strict-mcp-config", "--safe-mode",
202
+ "--tools", "", "--allowedTools", "mcp__topline-build__platform_inspect_artifact_app",
203
+ "--permission-mode", "dontAsk", "--no-chrome", "--disable-slash-commands",
204
+ "--no-session-persistence", "--max-budget-usd", "0.25",
205
+ "-p", "--output-format", "stream-json", prompt
206
+ ], scratch, 180000, strictAgentEnvironment(process.env));
207
+ if (!result.ok) {
208
+ return authUnavailable(result.output)
209
+ ? skipped("provider_credentials_or_client_session_unavailable")
210
+ : failed("client_mediated_read_failed", result.exitCode);
211
+ }
212
+ const usedTool = hasToolCallEvidence(result.output, "platform_inspect_artifact_app");
213
+ const certified = hasAssistantCertificationEvidence(result.output);
214
+ return usedTool && certified
215
+ ? passed({ tool: "platform_inspect_artifact_app", providerModelUsed: true })
216
+ : failed("client_mediated_tool_evidence_missing", result.exitCode);
217
+ }
218
+
219
+ async function directToolDiscovery(clientName, apiUrl, workspaceRoot) {
220
+ const opts = parseHarnessOptions([], {
221
+ TOPLINE_API_URL: apiUrl,
222
+ TOPLINE_WORKSPACE_ROOT: workspaceRoot,
223
+ TOPLINE_MCP_TIMEOUT_MS: process.env.TOPLINE_MCP_TIMEOUT_MS || "120000"
224
+ });
225
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
226
+ const server = createHarnessMcpStdioServer(opts);
227
+ const client = new Client({ name: `${clientName}-certification`, version: "1" }, { capabilities: {} });
228
+ try {
229
+ await server.connect(serverTransport);
230
+ await client.connect(clientTransport);
231
+ const listed = await client.listTools();
232
+ const names = new Set(listed.tools.map((tool) => tool.name));
233
+ const missing = EXPECTED_TOOLS.filter((name) => !names.has(name));
234
+ return missing.length === 0
235
+ ? passed({ toolCount: listed.tools.length, protocolVersion: manifest.protocol.defaultVersion })
236
+ : failed("required_tools_missing", null, { missing });
237
+ } catch {
238
+ return failed("mcp_tools_list_failed");
239
+ } finally {
240
+ await client.close().catch(() => undefined);
241
+ await server.close().catch(() => undefined);
242
+ }
243
+ }
244
+
245
+ async function certifyProtocol({ apiUrl, token, appDir, workspaceRoot }) {
246
+ const opts = parseHarnessOptions([], {
247
+ TOPLINE_API_URL: apiUrl,
248
+ TOPLINE_MCP_TOKEN: token || "",
249
+ TOPLINE_WORKSPACE_ROOT: workspaceRoot,
250
+ TOPLINE_MCP_TIMEOUT_MS: process.env.TOPLINE_MCP_TIMEOUT_MS || "120000"
251
+ });
252
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
253
+ const server = createHarnessMcpStdioServer(opts);
254
+ const client = new Client({ name: "topline-build-live-certification", version: "1" }, { capabilities: {} });
255
+ try {
256
+ await server.connect(serverTransport);
257
+ await client.connect(clientTransport);
258
+ const listed = await client.listTools();
259
+ for (const name of EXPECTED_TOOLS) {
260
+ if (!listed.tools.some((tool) => tool.name === name)) return failed("required_tools_missing");
261
+ }
262
+ const invalid = await client.callTool({ name: "platform_inspect_artifact_app", arguments: { appDir, unexpected: true } });
263
+ const error = invalid.structuredContent?.error;
264
+ if (!invalid.isError || typeof error?.code !== "string" || typeof error?.retryable !== "boolean" || typeof error?.recoveryAction !== "string") {
265
+ return failed("structured_failure_contract_missing");
266
+ }
267
+ const inspected = structured(await client.callTool({ name: "platform_inspect_artifact_app", arguments: { appDir } }));
268
+ if (inspected.packageReady !== true) return failed("artifact_package_not_ready");
269
+ const planEvidence = {
270
+ status: "passed",
271
+ readOnlyTool: "platform_inspect_artifact_app",
272
+ packageReady: true,
273
+ bundleSha256: inspected.bundle.sha256,
274
+ structuredFailure: { code: error.code, retryable: error.retryable, recoveryPresent: true }
275
+ };
276
+ if (args.phase === "plan") {
277
+ return passed({ plan: planEvidence, apply: skipped("operator_review_required") });
278
+ }
279
+ if (!token) return skipped("manual_certification_token_missing", { plan: planEvidence });
280
+ if (!args.expectedBundleSha256) return failed("expected_bundle_sha256_required_for_apply", null, { plan: planEvidence });
281
+ if (args.expectedBundleSha256 !== inspected.bundle.sha256) return failed("reviewed_bundle_sha256_changed", null, { plan: planEvidence });
282
+
283
+ const deployed = structured(await client.callTool({
284
+ name: "platform_deploy_artifact_app",
285
+ arguments: {
286
+ appDir,
287
+ expectedBundleSha256: args.expectedBundleSha256,
288
+ title: `Build MCP certification ${new Date().toISOString()}`
289
+ }
290
+ }));
291
+ if (deployed.bundle?.sha256 !== args.expectedBundleSha256) return failed("deployed_bundle_sha256_mismatch");
292
+ const completed = deployed.operationId
293
+ ? structured(await client.callTool({
294
+ name: "platform_wait_artifact_deploy",
295
+ arguments: { artifactId: deployed.artifactId, operationId: deployed.operationId, timeoutSeconds: 600 }
296
+ }))
297
+ : deployed.deploy;
298
+ const smoke = structured(await client.callTool({ name: "platform_run_artifact_smoke", arguments: { artifactId: deployed.artifactId } }));
299
+ const validation = structured(await client.callTool({ name: "platform_validate_artifact", arguments: { artifactId: deployed.artifactId } }));
300
+ const deletePlan = structured(await client.callTool({ name: "platform_plan_artifact_draft_delete", arguments: { artifactId: deployed.artifactId } }));
301
+ let cleanup = skipped("cleanup_not_approved");
302
+ if (process.env.TOPLINE_CERTIFICATION_CLEANUP === "1") {
303
+ cleanup = passed(structured(await client.callTool({
304
+ name: "platform_delete_artifact_draft",
305
+ arguments: { artifactId: deployed.artifactId, confirmArtifactId: deployed.artifactId, planToken: deletePlan.planToken }
306
+ })));
307
+ }
308
+ if (validation.healthy !== true) return failed("artifact_validation_unhealthy");
309
+ return passed({
310
+ plan: planEvidence,
311
+ apply: {
312
+ status: "passed",
313
+ reviewedWriteTool: "platform_deploy_artifact_app",
314
+ exactBundleApplied: true,
315
+ artifactId: deployed.artifactId,
316
+ operationId: deployed.operationId ?? null,
317
+ taskStatus: completed?.taskStatus ?? null,
318
+ smokePhase: smoke.phase,
319
+ healthy: true,
320
+ deleteEffectsReviewed: Boolean(deletePlan.effects),
321
+ cleanup
322
+ }
323
+ });
324
+ } catch (error) {
325
+ return failed(classifyError(error));
326
+ } finally {
327
+ await client.close().catch(() => undefined);
328
+ await server.close().catch(() => undefined);
329
+ }
330
+ }
331
+
332
+ function selfTestReport() {
333
+ const toolName = "platform_inspect_artifact_app";
334
+ const promptOnly = JSON.stringify({ type: "user", content: `Call ${toolName} and print TOPLINE_CLIENT_READ_CERTIFIED.` });
335
+ const toolEvent = JSON.stringify({ type: "item.completed", item: { type: "mcp_tool_call", tool: toolName } });
336
+ const assistantEvent = JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "TOPLINE_CLIENT_READ_CERTIFIED" } });
337
+ const sanitized = withoutManualToken({
338
+ SAFE_VALUE: "kept",
339
+ TOPLINE_MCP_TOKEN: "topline-secret",
340
+ PLATFORM_MCP_TOKEN: "platform-secret",
341
+ OVERSITE_MCP_TOKEN: "legacy-secret"
342
+ });
343
+ const strictEnvironment = strictAgentEnvironment({
344
+ PATH: "kept-path",
345
+ USERPROFILE: "kept-profile",
346
+ AWS_ACCESS_KEY_ID: "aws-secret",
347
+ DATABASE_URL: "database-secret",
348
+ OPENAI_API_KEY: "openai-secret",
349
+ ANTHROPIC_API_KEY: "anthropic-secret",
350
+ CURSOR_API_KEY: "cursor-secret",
351
+ TOPLINE_MCP_TOKEN: "topline-secret"
352
+ });
353
+ const optedInStrictEnvironment = strictAgentEnvironment({
354
+ PATH: "kept-path",
355
+ ANTHROPIC_API_KEY: "anthropic-secret",
356
+ TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY: "1"
357
+ });
358
+ if (hasToolCallEvidence(promptOnly, toolName)
359
+ || !hasToolCallEvidence(toolEvent, toolName)
360
+ || hasAssistantCertificationEvidence(promptOnly)
361
+ || !hasAssistantCertificationEvidence(assistantEvent)
362
+ || sanitized.SAFE_VALUE !== "kept"
363
+ || "TOPLINE_MCP_TOKEN" in sanitized
364
+ || "PLATFORM_MCP_TOKEN" in sanitized
365
+ || "OVERSITE_MCP_TOKEN" in sanitized
366
+ || strictEnvironment.PATH !== "kept-path"
367
+ || strictEnvironment.USERPROFILE !== "kept-profile"
368
+ || ["AWS_ACCESS_KEY_ID", "DATABASE_URL", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "CURSOR_API_KEY", "TOPLINE_MCP_TOKEN"].some((key) => key in strictEnvironment)
369
+ || optedInStrictEnvironment.ANTHROPIC_API_KEY !== "anthropic-secret"
370
+ || "TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY" in optedInStrictEnvironment) {
371
+ throw new Error("client_tool_evidence_self_test_failed");
372
+ }
373
+ return {
374
+ schemaVersion: 1,
375
+ status: "passed",
376
+ evidenceMode: "simulated",
377
+ package: { name: packageJson.name, version: packageJson.version },
378
+ protocol: { transport: manifest.protocol.transport, version: manifest.protocol.defaultVersion },
379
+ phase: args.phase,
380
+ auth: {
381
+ preferred: "oauth_browser_approval",
382
+ certificationMode: "self_test",
383
+ boundary: "Self-test uses no credentials or external clients."
384
+ },
385
+ prerequisites: { apiUrl: "self_test", appDir: "self_test", credentials: "self_test" },
386
+ clients: args.clients.map((client) => ({
387
+ client,
388
+ installation: passed({ simulated: true }),
389
+ configuration: passed({ simulated: true, scope: "ephemeral" }),
390
+ protocolToolDiscovery: passed({ simulated: true, toolCount: manifest.tools.length, protocolVersion: manifest.protocol.defaultVersion }),
391
+ clientMediatedRead: passed({ simulated: true, tool: "platform_inspect_artifact_app", providerModelUsed: false })
392
+ })),
393
+ workflow: passed({
394
+ plan: { status: "passed", readOnlyTool: "platform_inspect_artifact_app", structuredFailure: { recoveryPresent: true } },
395
+ apply: { status: "passed", reviewedWriteTool: "platform_deploy_artifact_app", exactBundleApplied: true, simulated: true }
396
+ })
397
+ };
398
+ }
399
+
400
+ function parseArgs(values) {
401
+ const parsed = { agentMediated: false, clients: [...CLIENTS], expectedBundleSha256: null, output: null, phase: "plan", requireAll: false, selfTest: false };
402
+ for (let index = 0; index < values.length; index += 1) {
403
+ const value = values[index];
404
+ if (value === "--agent-mediated") parsed.agentMediated = true;
405
+ else if (value === "--clients") parsed.clients = required(values[++index], value).split(",").map((entry) => entry.trim());
406
+ else if (value === "--expected-bundle-sha256") parsed.expectedBundleSha256 = required(values[++index], value).toLowerCase();
407
+ else if (value === "--output") parsed.output = required(values[++index], value);
408
+ else if (value === "--phase") parsed.phase = required(values[++index], value);
409
+ else if (value === "--require-all") parsed.requireAll = true;
410
+ else if (value === "--self-test") parsed.selfTest = true;
411
+ else throw new Error(`Unknown argument: ${value}`);
412
+ }
413
+ if (!parsed.clients.length || parsed.clients.some((name) => !CLIENTS.includes(name))) throw new Error("--clients must contain codex, claude, and/or cursor.");
414
+ if (new Set(parsed.clients).size !== parsed.clients.length) throw new Error("--clients must not contain duplicates.");
415
+ if (!['plan', 'apply'].includes(parsed.phase)) throw new Error("--phase must be plan or apply.");
416
+ if (parsed.expectedBundleSha256 && !/^[0-9a-f]{64}$/.test(parsed.expectedBundleSha256)) throw new Error("--expected-bundle-sha256 must be 64 hexadecimal characters.");
417
+ return parsed;
418
+ }
419
+
420
+ function clientCommand(name) {
421
+ const configured = name === "codex"
422
+ ? process.env.TOPLINE_CERTIFICATION_CODEX_BIN
423
+ : name === "claude"
424
+ ? process.env.TOPLINE_CERTIFICATION_CLAUDE_BIN
425
+ : process.env.TOPLINE_CERTIFICATION_CURSOR_BIN;
426
+ const executable = configured || (name === "cursor" ? "cursor-agent" : name);
427
+ if (process.platform !== "win32") return { executable, prefix: [] };
428
+ const found = spawnSync("where.exe", [executable], { encoding: "utf8", windowsHide: true });
429
+ const candidates = (found.stdout || "").split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
430
+ const native = candidates.find((entry) => /\.exe$/i.test(entry));
431
+ if (native) return { executable: native, prefix: [] };
432
+ const adjacentPowerShell = candidates.find((entry) => /\.cmd$/i.test(entry) && existsSync(entry.replace(/\.cmd$/i, ".ps1")))?.replace(/\.cmd$/i, ".ps1");
433
+ const powerShell = candidates.find((entry) => /\.ps1$/i.test(entry)) || adjacentPowerShell;
434
+ if (powerShell) return { executable: "powershell.exe", prefix: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", powerShell] };
435
+ return { executable, prefix: [] };
436
+ }
437
+
438
+ function run(command, commandArgs, cwd, timeout = 30000, env = withoutManualToken(process.env)) {
439
+ const result = spawnSync(command.executable, [...command.prefix, ...commandArgs], {
440
+ cwd,
441
+ encoding: "utf8",
442
+ env,
443
+ timeout,
444
+ windowsHide: true
445
+ });
446
+ return {
447
+ ok: result.status === 0,
448
+ missing: result.error?.code === "ENOENT",
449
+ exitCode: result.status,
450
+ stdout: result.stdout || "",
451
+ output: `${result.stdout || ""}\n${result.stderr || ""}`
452
+ };
453
+ }
454
+
455
+ function authUnavailable(output) {
456
+ return /not logged in|not authenticated|authentication required|api key|login required|please login|sign in/i.test(output);
457
+ }
458
+
459
+ function hasToolCallEvidence(output, toolName) {
460
+ for (const line of output.split(/\r?\n/)) {
461
+ if (!line.trim().startsWith("{")) continue;
462
+ try {
463
+ if (containsToolCall(JSON.parse(line), toolName)) return true;
464
+ } catch {
465
+ // Client diagnostics may be mixed with JSONL. Only structured tool events count.
466
+ }
467
+ }
468
+ return false;
469
+ }
470
+
471
+ function containsToolCall(value, toolName) {
472
+ if (!value || typeof value !== "object") return false;
473
+ const type = typeof value.type === "string" ? value.type : "";
474
+ if (/tool(?:_|-)?(?:use|call)|mcp_tool/i.test(type) && JSON.stringify(value).includes(toolName)) return true;
475
+ return Object.values(value).some((child) => Array.isArray(child)
476
+ ? child.some((entry) => containsToolCall(entry, toolName))
477
+ : containsToolCall(child, toolName));
478
+ }
479
+
480
+ function hasAssistantCertificationEvidence(output) {
481
+ for (const line of output.split(/\r?\n/)) {
482
+ if (!line.trim().startsWith("{")) continue;
483
+ try {
484
+ if (containsAssistantCertificationEvidence(JSON.parse(line))) return true;
485
+ } catch {
486
+ // Client diagnostics may be mixed with JSONL. Only structured assistant output counts.
487
+ }
488
+ }
489
+ return false;
490
+ }
491
+
492
+ function containsAssistantCertificationEvidence(value) {
493
+ if (!value || typeof value !== "object") return false;
494
+ if (value.role === "user" || value.type === "user") return false;
495
+ const type = typeof value.type === "string" ? value.type : "";
496
+ const role = typeof value.role === "string" ? value.role : "";
497
+ if ((role === "assistant" || /agent_message|assistant|result/i.test(type))
498
+ && JSON.stringify(value).includes("TOPLINE_CLIENT_READ_CERTIFIED")) return true;
499
+ return Object.values(value).some((child) => Array.isArray(child)
500
+ ? child.some((entry) => containsAssistantCertificationEvidence(entry))
501
+ : containsAssistantCertificationEvidence(child));
502
+ }
503
+
504
+ function withoutManualToken(source) {
505
+ const env = { ...source };
506
+ delete env.TOPLINE_MCP_TOKEN;
507
+ delete env.PLATFORM_MCP_TOKEN;
508
+ delete env.OVERSITE_MCP_TOKEN;
509
+ return env;
510
+ }
511
+
512
+ function strictAgentEnvironment(source) {
513
+ const allowed = new Set([
514
+ "ALL_PROXY", "APPDATA", "CI", "COMSPEC", "FORCE_COLOR", "HTTP_PROXY", "HTTPS_PROXY",
515
+ "LANG", "LC_ALL", "LOCALAPPDATA", "NODE_EXTRA_CA_CERTS", "NO_COLOR", "NO_PROXY", "PATH",
516
+ "PATHEXT", "PROGRAMDATA", "SSL_CERT_DIR", "SSL_CERT_FILE", "SYSTEMROOT", "TEMP", "TMP",
517
+ "USERPROFILE", "WINDIR"
518
+ ]);
519
+ const env = Object.fromEntries(Object.entries(source).filter(([key]) => allowed.has(key.toUpperCase())));
520
+ if (source.TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY === "1" && source.ANTHROPIC_API_KEY) {
521
+ env.ANTHROPIC_API_KEY = source.ANTHROPIC_API_KEY;
522
+ }
523
+ return env;
524
+ }
525
+
526
+ function structured(result) {
527
+ if (result.isError) throw new Error(result.structuredContent?.error?.code || "mcp_tool_error");
528
+ return result.structuredContent;
529
+ }
530
+
531
+ function classifyError(error) {
532
+ const message = error instanceof Error ? error.message : "unknown";
533
+ return /^[a-z0-9_:-]+$/.test(message) ? message.slice(0, 120) : "certification_workflow_failed";
534
+ }
535
+
536
+ function passed(details = {}) {
537
+ return { status: "passed", ...details };
538
+ }
539
+
540
+ function skipped(reason, details = {}) {
541
+ return { status: "skipped", reason, ...details };
542
+ }
543
+
544
+ function failed(reason, exitCode = null, details = {}) {
545
+ return { status: "failed", reason, exitCode, ...details };
546
+ }
547
+
548
+ function required(value, flag) {
549
+ if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value.`);
550
+ return value;
551
+ }