@voiden/runner 2.3.0-beta.1 → 2.3.0-beta.10
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/CHANGELOG.md +68 -0
- package/bundled-runners/versions.json +2 -0
- package/bundled-runners/voiden-faker-runner.js +6 -13
- package/bundled-runners/voiden-mcp-client-runner.js +1 -0
- package/bundled-runners/voiden-mcp-tool-runner.js +143 -0
- package/dist/cliPrint.d.ts +7 -0
- package/dist/cliPrint.d.ts.map +1 -0
- package/dist/cliPrint.js +166 -0
- package/dist/cliPrint.js.map +1 -0
- package/dist/envFile.d.ts +12 -1
- package/dist/envFile.d.ts.map +1 -1
- package/dist/envFile.js +77 -19
- package/dist/envFile.js.map +1 -1
- package/dist/headlessContext.d.ts.map +1 -1
- package/dist/headlessContext.js +34 -0
- package/dist/headlessContext.js.map +1 -1
- package/dist/index.js +268 -9
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +8 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +10 -2
- package/dist/lib.js.map +1 -1
- package/dist/mcpServing.d.ts +39 -0
- package/dist/mcpServing.d.ts.map +1 -0
- package/dist/mcpServing.js +154 -0
- package/dist/mcpServing.js.map +1 -0
- package/dist/mcpToolCapability.d.ts +158 -0
- package/dist/mcpToolCapability.d.ts.map +1 -0
- package/dist/mcpToolCapability.js +87 -0
- package/dist/mcpToolCapability.js.map +1 -0
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +4 -0
- package/dist/plugins/loader.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +7 -2
- package/dist/runner.js.map +1 -1
- package/dist/runtimeVars.d.ts.map +1 -1
- package/dist/runtimeVars.js +18 -0
- package/dist/runtimeVars.js.map +1 -1
- package/dist/toolRegistry.d.ts +102 -0
- package/dist/toolRegistry.d.ts.map +1 -0
- package/dist/toolRegistry.js +38 -0
- package/dist/toolRegistry.js.map +1 -0
- package/package.json +11 -5
package/dist/index.js
CHANGED
|
@@ -6,6 +6,12 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { runVoidFile } from './runner.js';
|
|
8
8
|
import { resolveFiles } from './discovery.js';
|
|
9
|
+
import { discoverTools, verifyTools, validateTools, upsertToolStatus, registerToolsFromDecisions, planServedTools, getCommitSha } from './mcpToolCapability.js';
|
|
10
|
+
import { registerFixedTools } from './mcpServing.js';
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
14
|
+
import { createServer as createHttpServer } from 'node:http';
|
|
9
15
|
import { loadEnabledPlugins } from './plugins/loader.js';
|
|
10
16
|
import { exportToCsv } from './report/csv.js';
|
|
11
17
|
import { sendMailReport } from './report/mail.js';
|
|
@@ -429,8 +435,10 @@ program
|
|
|
429
435
|
' voiden-runner run auth.void\n' +
|
|
430
436
|
' voiden-runner run ./requests/\n' +
|
|
431
437
|
' voiden-runner run auth.void users.void ./smoke/\n' +
|
|
432
|
-
' voiden-runner run ./ --env .env.staging --bail\n'
|
|
438
|
+
' voiden-runner run ./ --env .env.staging --bail\n' +
|
|
439
|
+
' voiden-runner run ./ --env .voiden/env-public.yaml --environment staging\n')
|
|
433
440
|
.option('-e, --env <path>', 'Path to .env or .yaml file for variable substitution')
|
|
441
|
+
.option('--environment <name>', 'Scope --env to one named environment in a multi-environment YAML file (e.g. "dev") instead of merging every environment in it together')
|
|
434
442
|
.option('--env-var <key=value>', 'Individual environment variable override (can be used multiple times)', (val, memo) => {
|
|
435
443
|
memo.push(val);
|
|
436
444
|
return memo;
|
|
@@ -470,7 +478,7 @@ program
|
|
|
470
478
|
process.exit(EXIT_USAGE_ERROR);
|
|
471
479
|
}
|
|
472
480
|
try {
|
|
473
|
-
Object.assign(env, loadEnvFile(envPath));
|
|
481
|
+
Object.assign(env, loadEnvFile(envPath, opts.environment));
|
|
474
482
|
}
|
|
475
483
|
catch (err) {
|
|
476
484
|
console.error(chalk.red(` ✗ ${err.message}`));
|
|
@@ -1263,7 +1271,7 @@ pluginCmd
|
|
|
1263
1271
|
// ── voiden-runner mcp ─────────────────────────────────────────────────────────
|
|
1264
1272
|
//
|
|
1265
1273
|
// Enables the AI-agent loop for CLI-only users (no Voiden app installed):
|
|
1266
|
-
// registers @voiden/mcp
|
|
1274
|
+
// registers @voiden/mcp with Claude Code / Codex, and installs a
|
|
1267
1275
|
// standalone skill teaching the run/verify/write-back workflow. The Voiden
|
|
1268
1276
|
// app's own Settings toggle does the equivalent for desktop users, reusing
|
|
1269
1277
|
// the same registration helpers from mcpInstall.ts.
|
|
@@ -1276,23 +1284,25 @@ function resolveMcpTargets(opts) {
|
|
|
1276
1284
|
}
|
|
1277
1285
|
const mcpCmd = program
|
|
1278
1286
|
.command('mcp')
|
|
1279
|
-
.description('Enable AI-agent integration — registers
|
|
1287
|
+
.description('Enable AI-agent integration — registers this project\'s fixed-tools MCP server and installs a run/verify skill');
|
|
1280
1288
|
mcpCmd
|
|
1281
1289
|
.command('install')
|
|
1282
|
-
.description('Register
|
|
1290
|
+
.description('Register this project with Claude Code and/or Codex — points .mcp.json / config.toml at ' +
|
|
1291
|
+
'`voiden-runner mcp serve` (the same 4 fixed tools, standalone, no other Voiden package ' +
|
|
1292
|
+
'required), and installs a skill teaching the run/verify/write-back loop.\n\n' +
|
|
1283
1293
|
' Examples:\n' +
|
|
1284
1294
|
' voiden-runner mcp install # both Claude Code and Codex\n' +
|
|
1285
1295
|
' voiden-runner mcp install --claude # Claude Code only\n' +
|
|
1286
1296
|
' voiden-runner mcp install -p ./my-project # register against a specific project dir (default: cwd)\n' +
|
|
1287
|
-
' voiden-runner mcp install --local-server ./dist/index.js #
|
|
1297
|
+
' voiden-runner mcp install --local-server ./dist/index.js # point at a local build instead of npx\n')
|
|
1288
1298
|
.option('--claude', 'Install for Claude Code only')
|
|
1289
1299
|
.option('--codex', 'Install for Codex only')
|
|
1290
1300
|
.option('-p, --project <path>', 'Project directory to register the MCP server against', '.')
|
|
1291
|
-
.option('--local-server <path>', 'Use `node <path
|
|
1301
|
+
.option('--local-server <path>', 'Use `node <path> mcp serve` instead of `npx -y @voiden/runner mcp serve` — for testing against a local build')
|
|
1292
1302
|
.action((opts) => {
|
|
1293
1303
|
const targets = resolveMcpTargets(opts);
|
|
1294
1304
|
const serverCommand = opts.localServer
|
|
1295
|
-
? { command: 'node', args: [resolve(opts.localServer), resolve(opts.project)] }
|
|
1305
|
+
? { command: 'node', args: [resolve(opts.localServer), 'mcp', 'serve', resolve(opts.project)] }
|
|
1296
1306
|
: undefined;
|
|
1297
1307
|
const installed = installMcpIntegration(opts.project, targets, MCP_SKILL_MARKDOWN, serverCommand);
|
|
1298
1308
|
if (installed.length === 0) {
|
|
@@ -1301,7 +1311,7 @@ mcpCmd
|
|
|
1301
1311
|
}
|
|
1302
1312
|
console.log();
|
|
1303
1313
|
for (const target of installed) {
|
|
1304
|
-
console.log(chalk.green(` ✓ ${target === 'claude' ? 'Claude Code' : 'Codex'}`) + chalk.gray(` — skill installed,
|
|
1314
|
+
console.log(chalk.green(` ✓ ${target === 'claude' ? 'Claude Code' : 'Codex'}`) + chalk.gray(` — skill installed, fixed-tools MCP server registered for ${resolve(opts.project)}`));
|
|
1305
1315
|
}
|
|
1306
1316
|
if (serverCommand) {
|
|
1307
1317
|
console.log(chalk.gray(` Using local build: node ${serverCommand.args[0]}`));
|
|
@@ -1342,5 +1352,254 @@ mcpCmd
|
|
|
1342
1352
|
console.log(` server registered: ${status.codex.serverRegistered ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1343
1353
|
console.log();
|
|
1344
1354
|
});
|
|
1355
|
+
mcpCmd
|
|
1356
|
+
.command('serve [path]')
|
|
1357
|
+
.description('Serve this project as an MCP server — the same tools @voiden/mcp exposes ' +
|
|
1358
|
+
'(list/run/write plus declared /tool capabilities), over stdio (default) or HTTP.\n\n' +
|
|
1359
|
+
' Examples:\n' +
|
|
1360
|
+
' voiden-runner mcp serve # stdio, current directory\n' +
|
|
1361
|
+
' voiden-runner mcp serve ./api # stdio, specific project\n' +
|
|
1362
|
+
' voiden-runner mcp serve --http --port 3900 # HTTP on 127.0.0.1:3900\n' +
|
|
1363
|
+
' voiden-runner mcp serve --check # dry run — print what would be served, no live server\n')
|
|
1364
|
+
.option('--http', 'Serve over streamable HTTP instead of stdio')
|
|
1365
|
+
.option('-p, --port <port>', 'HTTP port (only with --http)', '3000')
|
|
1366
|
+
.option('--host <host>', 'HTTP bind address (only with --http) — binding beyond 127.0.0.1 is a real exposure risk', '127.0.0.1')
|
|
1367
|
+
.option('-e, --env <path>', 'Path to .env or .yaml file for variable substitution')
|
|
1368
|
+
.option('--environment <name>', 'Scope --env to one named environment in a multi-environment YAML file (e.g. "dev") instead of merging every environment in it together')
|
|
1369
|
+
.option('--check', 'Print what would be served and exit, without starting a live server')
|
|
1370
|
+
.action(async (path, opts) => {
|
|
1371
|
+
const projectRoot = resolve(path ?? '.');
|
|
1372
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
|
|
1373
|
+
if (opts.env) {
|
|
1374
|
+
const envPath = resolve(opts.env);
|
|
1375
|
+
if (!existsSync(envPath)) {
|
|
1376
|
+
console.error(chalk.red(`Env file not found: ${envPath}`));
|
|
1377
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1378
|
+
}
|
|
1379
|
+
try {
|
|
1380
|
+
Object.assign(env, loadEnvFile(envPath, opts.environment));
|
|
1381
|
+
}
|
|
1382
|
+
catch (err) {
|
|
1383
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
1384
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
if (opts.check) {
|
|
1388
|
+
// Dry run — no live server. Same underlying decision function real
|
|
1389
|
+
// serving uses, so this can never disagree with what actually gets
|
|
1390
|
+
// registered.
|
|
1391
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1392
|
+
const decisions = await planServedTools(projectRoot, env, activePlugins);
|
|
1393
|
+
console.log(`\n${decisions.length} /tool block(s) found in ${projectRoot}\n`);
|
|
1394
|
+
for (const d of decisions) {
|
|
1395
|
+
if (d.excluded) {
|
|
1396
|
+
console.log(` [EXCLUDED] ${d.tool.name}`);
|
|
1397
|
+
for (const reason of d.excludedReasons ?? [])
|
|
1398
|
+
console.log(` ${reason}`);
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const label = d.served ? (d.descriptionNote ? `SERVED (${d.status.state})` : 'SERVED') : 'WITHDRAWN';
|
|
1402
|
+
console.log(` [${label}] ${d.tool.name} — ${d.status.state}${d.status.note ? `: ${d.status.note}` : ''}`);
|
|
1403
|
+
}
|
|
1404
|
+
console.log(chalk.gray(` (plus the 4 fixed tools: list_void_files, list_requests, run_request, write_result)`));
|
|
1405
|
+
console.log();
|
|
1406
|
+
const anyFailing = decisions.some((d) => d.excluded || d.status?.state === 'failing');
|
|
1407
|
+
process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
|
|
1408
|
+
}
|
|
1409
|
+
// Verification (real network calls) runs exactly once here, regardless
|
|
1410
|
+
// of transport — never repeated per HTTP request below.
|
|
1411
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1412
|
+
const decisions = await planServedTools(projectRoot, env, activePlugins);
|
|
1413
|
+
const commitSha = getCommitSha(projectRoot);
|
|
1414
|
+
const servedCount = decisions.filter((d) => d.served).length;
|
|
1415
|
+
// Shared across calls so {{process.xxx}} runtime variables chain the
|
|
1416
|
+
// same way they do for the stdio path and for @voiden/mcp.
|
|
1417
|
+
const runtimeVars = {};
|
|
1418
|
+
if (opts.http) {
|
|
1419
|
+
const port = Number(opts.port);
|
|
1420
|
+
const host = opts.host;
|
|
1421
|
+
// A fresh McpServer + transport per HTTP request — this is how the
|
|
1422
|
+
// SDK's own stateless example (examples/server/simpleStatelessStreamableHttp.js)
|
|
1423
|
+
// does it, not an arbitrary choice: reusing one transport across
|
|
1424
|
+
// requests returns 500s. Cheap: registration is just schema/handler
|
|
1425
|
+
// wiring against the already-computed `decisions`, no re-verification.
|
|
1426
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
1427
|
+
try {
|
|
1428
|
+
const requestServer = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
|
|
1429
|
+
registerFixedTools(requestServer, projectRoot, runtimeVars, activePlugins);
|
|
1430
|
+
registerToolsFromDecisions(requestServer, decisions, env, runtimeVars, activePlugins, commitSha, projectRoot);
|
|
1431
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
1432
|
+
await requestServer.connect(transport);
|
|
1433
|
+
res.on('close', () => {
|
|
1434
|
+
transport.close();
|
|
1435
|
+
requestServer.close();
|
|
1436
|
+
});
|
|
1437
|
+
await transport.handleRequest(req, res);
|
|
1438
|
+
}
|
|
1439
|
+
catch (err) {
|
|
1440
|
+
console.error(chalk.red(` ✗ Error handling MCP request: ${err?.message ?? String(err)}`));
|
|
1441
|
+
if (!res.headersSent) {
|
|
1442
|
+
res.writeHead(500, { 'content-type': 'application/json' }).end(JSON.stringify({
|
|
1443
|
+
jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null,
|
|
1444
|
+
}));
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
httpServer.listen(port, host, () => {
|
|
1449
|
+
console.error(chalk.green(` ✓ voiden-runner mcp serve — listening on http://${host}:${port}/mcp`));
|
|
1450
|
+
console.error(chalk.gray(` ${servedCount} tool(s) served (plus list_void_files, list_requests, run_request, write_result)`));
|
|
1451
|
+
if (host !== '127.0.0.1' && host !== 'localhost') {
|
|
1452
|
+
console.error(chalk.red(` ⚠ Bound to ${host} — reachable beyond this machine. Make sure that's intended.`));
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
// No process.exit() — stays alive until Ctrl-C, same as the stdio path below.
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
// stdio: one persistent server for the process lifetime — stdout is
|
|
1459
|
+
// reserved for the JSON-RPC stream, so nothing gets printed there.
|
|
1460
|
+
// Startup info goes to stderr only, same discipline @voiden/mcp's
|
|
1461
|
+
// own entrypoint already follows (it prints nothing).
|
|
1462
|
+
const server = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
|
|
1463
|
+
registerFixedTools(server, projectRoot, runtimeVars, activePlugins);
|
|
1464
|
+
registerToolsFromDecisions(server, decisions, env, runtimeVars, activePlugins, commitSha, projectRoot);
|
|
1465
|
+
await server.connect(new StdioServerTransport());
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
// ── voiden-runner tool ────────────────────────────────────────────────────────
|
|
1469
|
+
//
|
|
1470
|
+
// Discovery + verification for /tool blocks (voiden-mcp-tool plugin) — the
|
|
1471
|
+
// standalone CLI surface for the same discoverTools/verifyTools functions
|
|
1472
|
+
// @voiden/mcp will use for live agent-serving. No scheduling here:
|
|
1473
|
+
// `cadence` on a verify entry is a tag `--cadence` filters by, not something
|
|
1474
|
+
// this command enforces timing for — that's a human/CI decision, same as
|
|
1475
|
+
// deciding when to run `voiden-runner run` at all.
|
|
1476
|
+
const toolCmd = program
|
|
1477
|
+
.command('tool')
|
|
1478
|
+
.description('Discover and verify /tool blocks — capabilities declared for AI agents');
|
|
1479
|
+
toolCmd
|
|
1480
|
+
.command('list [paths...]')
|
|
1481
|
+
.description('List every /tool block found under the given path(s) (default: current directory) — does not execute anything')
|
|
1482
|
+
.option('--json', 'Output as JSON')
|
|
1483
|
+
.action(async (paths, opts) => {
|
|
1484
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
1485
|
+
const allTools = [];
|
|
1486
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1487
|
+
for (const p of targets) {
|
|
1488
|
+
allTools.push(...await discoverTools(resolve(p), { activePlugins }));
|
|
1489
|
+
}
|
|
1490
|
+
if (opts.json) {
|
|
1491
|
+
console.log(JSON.stringify(allTools, null, 2));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (allTools.length === 0) {
|
|
1495
|
+
console.log(chalk.yellow(' No /tool blocks found.'));
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
console.log();
|
|
1499
|
+
for (const tool of allTools) {
|
|
1500
|
+
console.log(chalk.bold(` ${tool.name}`) + chalk.gray(` — ${relative(process.cwd(), tool.filePath)}${tool.sectionLabel ? ` [${tool.sectionLabel}]` : ''}`));
|
|
1501
|
+
if (tool.description)
|
|
1502
|
+
console.log(chalk.gray(` ${tool.description}`));
|
|
1503
|
+
// onFailure is per verify-entry now, not one tool-wide setting —
|
|
1504
|
+
// summarize instead of showing a single (no-longer-existing) value.
|
|
1505
|
+
const onFailureValues = new Set(tool.verifies.map((v) => v.onFailure || 'withdraw'));
|
|
1506
|
+
const onFailureSummary = tool.verifies.length === 0 ? '—' : onFailureValues.size === 1 ? [...onFailureValues][0] : 'mixed';
|
|
1507
|
+
console.log(chalk.gray(` params: ${tool.params.length} verifies: ${tool.verifies.length} on-failure: ${onFailureSummary}`));
|
|
1508
|
+
console.log();
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
toolCmd
|
|
1512
|
+
.command('verify [paths...]')
|
|
1513
|
+
.description("Run each /tool block's verification requests and report verified / unverified / failing.\n\n" +
|
|
1514
|
+
' Examples:\n' +
|
|
1515
|
+
' voiden-runner tool verify\n' +
|
|
1516
|
+
' voiden-runner tool verify ./api/ --cadence nightly\n' +
|
|
1517
|
+
' voiden-runner tool verify --json --write\n')
|
|
1518
|
+
.option('--cadence <tag>', 'Only run verification requests tagged with this cadence — omit to run every entry regardless of tag')
|
|
1519
|
+
.option('--json', 'Output as JSON (suppresses normal output — useful for CI)')
|
|
1520
|
+
.option('--write', 'Write the computed status back into each /tool block. Off by default — verification always recomputes fresh and never trusts a stale write-back')
|
|
1521
|
+
.option('-e, --env <path>', 'Path to .env or .yaml file for variable substitution')
|
|
1522
|
+
.option('--environment <name>', 'Scope --env to one named environment in a multi-environment YAML file (e.g. "dev") instead of merging every environment in it together')
|
|
1523
|
+
.action(async (paths, opts) => {
|
|
1524
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
1525
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
|
|
1526
|
+
if (opts.env) {
|
|
1527
|
+
const envPath = resolve(opts.env);
|
|
1528
|
+
if (!existsSync(envPath)) {
|
|
1529
|
+
console.error(chalk.red(`Env file not found: ${envPath}`));
|
|
1530
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1531
|
+
}
|
|
1532
|
+
try {
|
|
1533
|
+
Object.assign(env, loadEnvFile(envPath, opts.environment));
|
|
1534
|
+
}
|
|
1535
|
+
catch (err) {
|
|
1536
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
1537
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1541
|
+
const allTools = [];
|
|
1542
|
+
for (const p of targets) {
|
|
1543
|
+
allTools.push(...await discoverTools(resolve(p), { activePlugins }));
|
|
1544
|
+
}
|
|
1545
|
+
if (allTools.length === 0) {
|
|
1546
|
+
if (opts.json)
|
|
1547
|
+
console.log(JSON.stringify({ tools: [], issues: [] }, null, 2));
|
|
1548
|
+
else
|
|
1549
|
+
console.log(chalk.yellow(' No /tool blocks found.'));
|
|
1550
|
+
process.exit(EXIT_SUCCESS);
|
|
1551
|
+
}
|
|
1552
|
+
const { validTools, issues } = await validateTools(resolve(targets[0]), allTools);
|
|
1553
|
+
const statuses = await verifyTools(validTools, { cadence: opts.cadence, env, activePlugins, projectRoot: resolve(targets[0]) });
|
|
1554
|
+
if (opts.write) {
|
|
1555
|
+
for (const status of statuses) {
|
|
1556
|
+
try {
|
|
1557
|
+
upsertToolStatus(status.tool.filePath, status.tool.toolBlockUid, { state: status.state, note: status.note });
|
|
1558
|
+
}
|
|
1559
|
+
catch (err) {
|
|
1560
|
+
console.error(chalk.red(` ✗ Failed to write status for "${status.tool.name}": ${err?.message ?? String(err)}`));
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
const anyFailing = statuses.some(s => s.state === 'failing') || issues.length > 0;
|
|
1565
|
+
if (opts.json) {
|
|
1566
|
+
console.log(JSON.stringify({ tools: statuses, issues }, null, 2));
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
console.log();
|
|
1570
|
+
// Structurally excluded tools (§1.6) — reported distinctly from a
|
|
1571
|
+
// verification failure: this is "the contract itself doesn't add up,"
|
|
1572
|
+
// not "the contract's proof failed." Neither ran nor counted below.
|
|
1573
|
+
const excludedNames = [...new Set(issues.map(i => i.tool.name))];
|
|
1574
|
+
for (const name of excludedNames) {
|
|
1575
|
+
console.log(` ${chalk.red('✗ excluded')} ${chalk.bold(name)}`);
|
|
1576
|
+
for (const issue of issues.filter(i => i.tool.name === name)) {
|
|
1577
|
+
console.log(chalk.gray(` [${issue.check}] ${issue.message}`));
|
|
1578
|
+
}
|
|
1579
|
+
console.log();
|
|
1580
|
+
}
|
|
1581
|
+
for (const status of statuses) {
|
|
1582
|
+
const icon = status.state === 'verified' ? chalk.green('✓ verified ') :
|
|
1583
|
+
status.state === 'failing' ? chalk.red('✗ failing ') :
|
|
1584
|
+
chalk.yellow('○ unverified');
|
|
1585
|
+
console.log(` ${icon} ${chalk.bold(status.tool.name)}` + chalk.gray(` — ${relative(process.cwd(), status.tool.filePath)}${status.tool.sectionLabel ? ` [${status.tool.sectionLabel}]` : ''}`));
|
|
1586
|
+
if (status.note)
|
|
1587
|
+
console.log(chalk.gray(` ${status.note}`));
|
|
1588
|
+
for (const r of status.results) {
|
|
1589
|
+
const rIcon = r.passed ? chalk.green('✓') : chalk.red('✗');
|
|
1590
|
+
console.log(` ${rIcon} ${r.entry.role}: ${r.entry.sectionLabel}` + (r.reason ? chalk.gray(` (${r.reason})`) : ''));
|
|
1591
|
+
}
|
|
1592
|
+
console.log();
|
|
1593
|
+
}
|
|
1594
|
+
const verified = statuses.filter(s => s.state === 'verified').length;
|
|
1595
|
+
const unverified = statuses.filter(s => s.state === 'unverified').length;
|
|
1596
|
+
const failing = statuses.filter(s => s.state === 'failing').length;
|
|
1597
|
+
console.log(chalk.bold(` ${statuses.length} tool(s)`) + chalk.gray(` — ${chalk.green(verified + ' verified')}, ${chalk.yellow(unverified + ' unverified')}, ${chalk.red(failing + ' failing')}`) + (excludedNames.length > 0 ? chalk.red(`, ${excludedNames.length} excluded`) : ''));
|
|
1598
|
+
if (opts.write)
|
|
1599
|
+
console.log(chalk.gray(' Status written back into each /tool block.'));
|
|
1600
|
+
console.log();
|
|
1601
|
+
}
|
|
1602
|
+
process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
|
|
1603
|
+
});
|
|
1345
1604
|
program.parse();
|
|
1346
1605
|
//# sourceMappingURL=index.js.map
|