@voiden/runner 2.3.0-beta.1 → 2.3.0-beta.2
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 +11 -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/headlessContext.d.ts.map +1 -1
- package/dist/headlessContext.js +34 -0
- package/dist/headlessContext.js.map +1 -1
- package/dist/index.js +249 -0
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +5 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +2 -0
- 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 +120 -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/toolRegistry.d.ts +93 -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 +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessContext.d.ts","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"headlessContext.d.ts","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,KAAK,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAA;AAEpG,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAe,GACvB,aAAa,CAoHf"}
|
package/dist/headlessContext.js
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
import { hookRegistry, requestOrchestrator, executeWebSocket, executeGrpc } from '@voiden/executors';
|
|
17
17
|
import { registerBlockSchema } from './blockSchemaRegistry.js';
|
|
18
18
|
import { registerRequestContainer as registerContainerDef } from './requestContainerRegistry.js';
|
|
19
|
+
import { registerToolProvider as registerToolProviderFn } from './toolRegistry.js';
|
|
20
|
+
import { registerMcpToolCapabilityProvider as registerMcpToolCapabilityProviderFn } from './mcpToolCapability.js';
|
|
21
|
+
import { collectVoidFiles } from './discovery.js';
|
|
22
|
+
import { runVoidFile, getRequestPreview } from './runner.js';
|
|
19
23
|
export function createHeadlessPluginContext(extensionId, verbose = false) {
|
|
20
24
|
return {
|
|
21
25
|
// ── Pipeline hook registration (for hook plugins: scripting, auth, etc.) ─
|
|
@@ -61,6 +65,20 @@ export function createHeadlessPluginContext(extensionId, verbose = false) {
|
|
|
61
65
|
},
|
|
62
66
|
// ── Verbosity ────────────────────────────────────────────────────────
|
|
63
67
|
verbose,
|
|
68
|
+
// ── Runner primitives (for the plugin owning the /tool block) ─────────
|
|
69
|
+
// A plugin's runner bundle can't `import` from @voiden/runner itself —
|
|
70
|
+
// it's the host loading the plugin, not an externalized dependency, and
|
|
71
|
+
// the standalone-installed plugin copy has no node_modules to resolve it
|
|
72
|
+
// from anyway. So the handful of core-internal functions the /tool
|
|
73
|
+
// capability's relocated discover/verify/serve logic needs are handed
|
|
74
|
+
// over as already-bound closures here instead, the same way
|
|
75
|
+
// `protocols.executeWebSocket` already exposes a core-internal
|
|
76
|
+
// implementation to socket plugins without them importing it.
|
|
77
|
+
runnerPrimitives: {
|
|
78
|
+
collectVoidFiles: (inputPath) => collectVoidFiles(inputPath),
|
|
79
|
+
runVoidFile: (filePath, options) => runVoidFile(filePath, options),
|
|
80
|
+
getRequestPreview: (blocks) => getRequestPreview(blocks),
|
|
81
|
+
},
|
|
64
82
|
// ── Reporting ────────────────────────────────────────────────────────
|
|
65
83
|
report: {
|
|
66
84
|
add: (_entry) => {
|
|
@@ -88,6 +106,22 @@ export function createHeadlessPluginContext(extensionId, verbose = false) {
|
|
|
88
106
|
registerRequestContainer: (def) => {
|
|
89
107
|
registerContainerDef(def);
|
|
90
108
|
},
|
|
109
|
+
// Host capability not yet in the published @voiden/sdk RunnerContext
|
|
110
|
+
// type, same treatment as registerRequestContainer above — lets the
|
|
111
|
+
// plugin owning the /tool block declare its own block→tool-declaration
|
|
112
|
+
// extraction function, so voiden-runner's tool discovery/verification
|
|
113
|
+
// works without hardcoding knowledge of that block's shape.
|
|
114
|
+
registerToolProvider: (extractFn) => {
|
|
115
|
+
registerToolProviderFn(extractFn);
|
|
116
|
+
},
|
|
117
|
+
// Host capability not yet in the published @voiden/sdk RunnerContext
|
|
118
|
+
// type, same treatment as registerToolProvider above — lets the plugin
|
|
119
|
+
// owning the /tool block hand back its discover/validate/verify/serve
|
|
120
|
+
// implementation (mcpToolCapability.ts's McpToolCapabilityProvider),
|
|
121
|
+
// so voiden-runner core never hardcodes that block's own semantics.
|
|
122
|
+
registerMcpToolCapabilityProvider: (p) => {
|
|
123
|
+
registerMcpToolCapabilityProviderFn(p);
|
|
124
|
+
},
|
|
91
125
|
},
|
|
92
126
|
};
|
|
93
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessContext.js","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAiB,mBAAmB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEnH,OAAO,EAAE,mBAAmB,EAAuB,MAAM,0BAA0B,CAAA;AACnF,OAAO,EAAE,wBAAwB,IAAI,oBAAoB,EAA4B,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"headlessContext.js","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"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"}
|
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';
|
|
@@ -1342,5 +1348,248 @@ mcpCmd
|
|
|
1342
1348
|
console.log(` server registered: ${status.codex.serverRegistered ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1343
1349
|
console.log();
|
|
1344
1350
|
});
|
|
1351
|
+
mcpCmd
|
|
1352
|
+
.command('serve [path]')
|
|
1353
|
+
.description('Serve this project as an MCP server — the same tools @voiden/mcp-server exposes ' +
|
|
1354
|
+
'(list/run/write plus declared /tool capabilities), over stdio (default) or HTTP.\n\n' +
|
|
1355
|
+
' Examples:\n' +
|
|
1356
|
+
' voiden-runner mcp serve # stdio, current directory\n' +
|
|
1357
|
+
' voiden-runner mcp serve ./api # stdio, specific project\n' +
|
|
1358
|
+
' voiden-runner mcp serve --http --port 3900 # HTTP on 127.0.0.1:3900\n' +
|
|
1359
|
+
' voiden-runner mcp serve --check # dry run — print what would be served, no live server\n')
|
|
1360
|
+
.option('--http', 'Serve over streamable HTTP instead of stdio')
|
|
1361
|
+
.option('-p, --port <port>', 'HTTP port (only with --http)', '3000')
|
|
1362
|
+
.option('--host <host>', 'HTTP bind address (only with --http) — binding beyond 127.0.0.1 is a real exposure risk', '127.0.0.1')
|
|
1363
|
+
.option('-e, --env <path>', 'Path to .env file for variable substitution')
|
|
1364
|
+
.option('--check', 'Print what would be served and exit, without starting a live server')
|
|
1365
|
+
.action(async (path, opts) => {
|
|
1366
|
+
const projectRoot = resolve(path ?? '.');
|
|
1367
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
|
|
1368
|
+
if (opts.env) {
|
|
1369
|
+
const envPath = resolve(opts.env);
|
|
1370
|
+
if (!existsSync(envPath)) {
|
|
1371
|
+
console.error(chalk.red(`Env file not found: ${envPath}`));
|
|
1372
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1373
|
+
}
|
|
1374
|
+
try {
|
|
1375
|
+
Object.assign(env, loadEnvFile(envPath));
|
|
1376
|
+
}
|
|
1377
|
+
catch (err) {
|
|
1378
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
1379
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (opts.check) {
|
|
1383
|
+
// Dry run — no live server. Same underlying decision function real
|
|
1384
|
+
// serving uses, so this can never disagree with what actually gets
|
|
1385
|
+
// registered.
|
|
1386
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1387
|
+
const decisions = await planServedTools(projectRoot, env, activePlugins);
|
|
1388
|
+
console.log(`\n${decisions.length} /tool block(s) found in ${projectRoot}\n`);
|
|
1389
|
+
for (const d of decisions) {
|
|
1390
|
+
if (d.excluded) {
|
|
1391
|
+
console.log(` [EXCLUDED] ${d.tool.name}`);
|
|
1392
|
+
for (const reason of d.excludedReasons ?? [])
|
|
1393
|
+
console.log(` ${reason}`);
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
const label = d.served ? (d.descriptionNote ? `SERVED (${d.status.state})` : 'SERVED') : 'WITHDRAWN';
|
|
1397
|
+
console.log(` [${label}] ${d.tool.name} — ${d.status.state}${d.status.note ? `: ${d.status.note}` : ''}`);
|
|
1398
|
+
}
|
|
1399
|
+
console.log(chalk.gray(` (plus the 4 fixed tools: list_void_files, list_requests, run_request, write_result)`));
|
|
1400
|
+
console.log();
|
|
1401
|
+
const anyFailing = decisions.some((d) => d.excluded || d.status?.state === 'failing');
|
|
1402
|
+
process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
|
|
1403
|
+
}
|
|
1404
|
+
// Verification (real network calls) runs exactly once here, regardless
|
|
1405
|
+
// of transport — never repeated per HTTP request below.
|
|
1406
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1407
|
+
const decisions = await planServedTools(projectRoot, env, activePlugins);
|
|
1408
|
+
const commitSha = getCommitSha(projectRoot);
|
|
1409
|
+
const servedCount = decisions.filter((d) => d.served).length;
|
|
1410
|
+
// Shared across calls so {{process.xxx}} runtime variables chain the
|
|
1411
|
+
// same way they do for the stdio path and for @voiden/mcp-server.
|
|
1412
|
+
const runtimeVars = {};
|
|
1413
|
+
if (opts.http) {
|
|
1414
|
+
const port = Number(opts.port);
|
|
1415
|
+
const host = opts.host;
|
|
1416
|
+
// A fresh McpServer + transport per HTTP request — this is how the
|
|
1417
|
+
// SDK's own stateless example (examples/server/simpleStatelessStreamableHttp.js)
|
|
1418
|
+
// does it, not an arbitrary choice: reusing one transport across
|
|
1419
|
+
// requests returns 500s. Cheap: registration is just schema/handler
|
|
1420
|
+
// wiring against the already-computed `decisions`, no re-verification.
|
|
1421
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
1422
|
+
try {
|
|
1423
|
+
const requestServer = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
|
|
1424
|
+
registerFixedTools(requestServer, projectRoot, runtimeVars, activePlugins);
|
|
1425
|
+
registerToolsFromDecisions(requestServer, decisions, env, runtimeVars, activePlugins, commitSha);
|
|
1426
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
1427
|
+
await requestServer.connect(transport);
|
|
1428
|
+
res.on('close', () => {
|
|
1429
|
+
transport.close();
|
|
1430
|
+
requestServer.close();
|
|
1431
|
+
});
|
|
1432
|
+
await transport.handleRequest(req, res);
|
|
1433
|
+
}
|
|
1434
|
+
catch (err) {
|
|
1435
|
+
console.error(chalk.red(` ✗ Error handling MCP request: ${err?.message ?? String(err)}`));
|
|
1436
|
+
if (!res.headersSent) {
|
|
1437
|
+
res.writeHead(500, { 'content-type': 'application/json' }).end(JSON.stringify({
|
|
1438
|
+
jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null,
|
|
1439
|
+
}));
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
httpServer.listen(port, host, () => {
|
|
1444
|
+
console.error(chalk.green(` ✓ voiden-runner mcp serve — listening on http://${host}:${port}/mcp`));
|
|
1445
|
+
console.error(chalk.gray(` ${servedCount} tool(s) served (plus list_void_files, list_requests, run_request, write_result)`));
|
|
1446
|
+
if (host !== '127.0.0.1' && host !== 'localhost') {
|
|
1447
|
+
console.error(chalk.red(` ⚠ Bound to ${host} — reachable beyond this machine. Make sure that's intended.`));
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
// No process.exit() — stays alive until Ctrl-C, same as the stdio path below.
|
|
1451
|
+
}
|
|
1452
|
+
else {
|
|
1453
|
+
// stdio: one persistent server for the process lifetime — stdout is
|
|
1454
|
+
// reserved for the JSON-RPC stream, so nothing gets printed there.
|
|
1455
|
+
// Startup info goes to stderr only, same discipline @voiden/mcp-server's
|
|
1456
|
+
// own entrypoint already follows (it prints nothing).
|
|
1457
|
+
const server = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
|
|
1458
|
+
registerFixedTools(server, projectRoot, runtimeVars, activePlugins);
|
|
1459
|
+
registerToolsFromDecisions(server, decisions, env, runtimeVars, activePlugins, commitSha);
|
|
1460
|
+
await server.connect(new StdioServerTransport());
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
// ── voiden-runner tool ────────────────────────────────────────────────────────
|
|
1464
|
+
//
|
|
1465
|
+
// Discovery + verification for /tool blocks (voiden-mcp-tool plugin) — the
|
|
1466
|
+
// standalone CLI surface for the same discoverTools/verifyTools functions
|
|
1467
|
+
// @voiden/mcp-server will use for live agent-serving. No scheduling here:
|
|
1468
|
+
// `cadence` on a verify entry is a tag `--cadence` filters by, not something
|
|
1469
|
+
// this command enforces timing for — that's a human/CI decision, same as
|
|
1470
|
+
// deciding when to run `voiden-runner run` at all.
|
|
1471
|
+
const toolCmd = program
|
|
1472
|
+
.command('tool')
|
|
1473
|
+
.description('Discover and verify /tool blocks — capabilities declared for AI agents');
|
|
1474
|
+
toolCmd
|
|
1475
|
+
.command('list [paths...]')
|
|
1476
|
+
.description('List every /tool block found under the given path(s) (default: current directory) — does not execute anything')
|
|
1477
|
+
.option('--json', 'Output as JSON')
|
|
1478
|
+
.action(async (paths, opts) => {
|
|
1479
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
1480
|
+
const allTools = [];
|
|
1481
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1482
|
+
for (const p of targets) {
|
|
1483
|
+
allTools.push(...await discoverTools(resolve(p), { activePlugins }));
|
|
1484
|
+
}
|
|
1485
|
+
if (opts.json) {
|
|
1486
|
+
console.log(JSON.stringify(allTools, null, 2));
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (allTools.length === 0) {
|
|
1490
|
+
console.log(chalk.yellow(' No /tool blocks found.'));
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
console.log();
|
|
1494
|
+
for (const tool of allTools) {
|
|
1495
|
+
console.log(chalk.bold(` ${tool.name}`) + chalk.gray(` — ${relative(process.cwd(), tool.filePath)}${tool.sectionLabel ? ` [${tool.sectionLabel}]` : ''}`));
|
|
1496
|
+
if (tool.description)
|
|
1497
|
+
console.log(chalk.gray(` ${tool.description}`));
|
|
1498
|
+
console.log(chalk.gray(` params: ${tool.params.length} verifies: ${tool.verifies.length} on-failure: ${tool.onFailure}`));
|
|
1499
|
+
console.log();
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
toolCmd
|
|
1503
|
+
.command('verify [paths...]')
|
|
1504
|
+
.description("Run each /tool block's verification requests and report verified / unverified / failing.\n\n" +
|
|
1505
|
+
' Examples:\n' +
|
|
1506
|
+
' voiden-runner tool verify\n' +
|
|
1507
|
+
' voiden-runner tool verify ./api/ --cadence nightly\n' +
|
|
1508
|
+
' voiden-runner tool verify --json --write\n')
|
|
1509
|
+
.option('--cadence <tag>', 'Only run verification requests tagged with this cadence — omit to run every entry regardless of tag')
|
|
1510
|
+
.option('--json', 'Output as JSON (suppresses normal output — useful for CI)')
|
|
1511
|
+
.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')
|
|
1512
|
+
.option('-e, --env <path>', 'Path to .env or .yaml file for variable substitution')
|
|
1513
|
+
.action(async (paths, opts) => {
|
|
1514
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
1515
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
|
|
1516
|
+
if (opts.env) {
|
|
1517
|
+
const envPath = resolve(opts.env);
|
|
1518
|
+
if (!existsSync(envPath)) {
|
|
1519
|
+
console.error(chalk.red(`Env file not found: ${envPath}`));
|
|
1520
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1521
|
+
}
|
|
1522
|
+
try {
|
|
1523
|
+
Object.assign(env, loadEnvFile(envPath));
|
|
1524
|
+
}
|
|
1525
|
+
catch (err) {
|
|
1526
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
1527
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const activePlugins = await loadEnabledPlugins();
|
|
1531
|
+
const allTools = [];
|
|
1532
|
+
for (const p of targets) {
|
|
1533
|
+
allTools.push(...await discoverTools(resolve(p), { activePlugins }));
|
|
1534
|
+
}
|
|
1535
|
+
if (allTools.length === 0) {
|
|
1536
|
+
if (opts.json)
|
|
1537
|
+
console.log(JSON.stringify({ tools: [], issues: [] }, null, 2));
|
|
1538
|
+
else
|
|
1539
|
+
console.log(chalk.yellow(' No /tool blocks found.'));
|
|
1540
|
+
process.exit(EXIT_SUCCESS);
|
|
1541
|
+
}
|
|
1542
|
+
const { validTools, issues } = await validateTools(resolve(targets[0]), allTools);
|
|
1543
|
+
const statuses = await verifyTools(validTools, { cadence: opts.cadence, env, activePlugins });
|
|
1544
|
+
if (opts.write) {
|
|
1545
|
+
for (const status of statuses) {
|
|
1546
|
+
try {
|
|
1547
|
+
upsertToolStatus(status.tool.filePath, status.tool.toolBlockUid, { state: status.state, note: status.note });
|
|
1548
|
+
}
|
|
1549
|
+
catch (err) {
|
|
1550
|
+
console.error(chalk.red(` ✗ Failed to write status for "${status.tool.name}": ${err?.message ?? String(err)}`));
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
const anyFailing = statuses.some(s => s.state === 'failing') || issues.length > 0;
|
|
1555
|
+
if (opts.json) {
|
|
1556
|
+
console.log(JSON.stringify({ tools: statuses, issues }, null, 2));
|
|
1557
|
+
}
|
|
1558
|
+
else {
|
|
1559
|
+
console.log();
|
|
1560
|
+
// Structurally excluded tools (§1.6) — reported distinctly from a
|
|
1561
|
+
// verification failure: this is "the contract itself doesn't add up,"
|
|
1562
|
+
// not "the contract's proof failed." Neither ran nor counted below.
|
|
1563
|
+
const excludedNames = [...new Set(issues.map(i => i.tool.name))];
|
|
1564
|
+
for (const name of excludedNames) {
|
|
1565
|
+
console.log(` ${chalk.red('✗ excluded')} ${chalk.bold(name)}`);
|
|
1566
|
+
for (const issue of issues.filter(i => i.tool.name === name)) {
|
|
1567
|
+
console.log(chalk.gray(` [${issue.check}] ${issue.message}`));
|
|
1568
|
+
}
|
|
1569
|
+
console.log();
|
|
1570
|
+
}
|
|
1571
|
+
for (const status of statuses) {
|
|
1572
|
+
const icon = status.state === 'verified' ? chalk.green('✓ verified ') :
|
|
1573
|
+
status.state === 'failing' ? chalk.red('✗ failing ') :
|
|
1574
|
+
chalk.yellow('○ unverified');
|
|
1575
|
+
console.log(` ${icon} ${chalk.bold(status.tool.name)}` + chalk.gray(` — ${relative(process.cwd(), status.tool.filePath)}${status.tool.sectionLabel ? ` [${status.tool.sectionLabel}]` : ''}`));
|
|
1576
|
+
if (status.note)
|
|
1577
|
+
console.log(chalk.gray(` ${status.note}`));
|
|
1578
|
+
for (const r of status.results) {
|
|
1579
|
+
const rIcon = r.passed ? chalk.green('✓') : chalk.red('✗');
|
|
1580
|
+
console.log(` ${rIcon} ${r.entry.role}: ${r.entry.sectionLabel}` + (r.reason ? chalk.gray(` (${r.reason})`) : ''));
|
|
1581
|
+
}
|
|
1582
|
+
console.log();
|
|
1583
|
+
}
|
|
1584
|
+
const verified = statuses.filter(s => s.state === 'verified').length;
|
|
1585
|
+
const unverified = statuses.filter(s => s.state === 'unverified').length;
|
|
1586
|
+
const failing = statuses.filter(s => s.state === 'failing').length;
|
|
1587
|
+
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`) : ''));
|
|
1588
|
+
if (opts.write)
|
|
1589
|
+
console.log(chalk.gray(' Status written back into each /tool block.'));
|
|
1590
|
+
console.log();
|
|
1591
|
+
}
|
|
1592
|
+
process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
|
|
1593
|
+
});
|
|
1345
1594
|
program.parse();
|
|
1346
1595
|
//# sourceMappingURL=index.js.map
|