@njeybe/nativ 2.0.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/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +334 -0
- package/bin/cli.js +31 -0
- package/dist/commands/bench.d.ts +8 -0
- package/dist/commands/bench.js +52 -0
- package/dist/commands/db.d.ts +22 -0
- package/dist/commands/db.js +415 -0
- package/dist/commands/doctor.d.ts +38 -0
- package/dist/commands/doctor.js +169 -0
- package/dist/commands/hook.d.ts +31 -0
- package/dist/commands/hook.js +105 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +144 -0
- package/dist/commands/mcp.d.ts +5 -0
- package/dist/commands/mcp.js +16 -0
- package/dist/commands/setup.d.ts +17 -0
- package/dist/commands/setup.js +51 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/task.d.ts +83 -0
- package/dist/commands/task.js +807 -0
- package/dist/commands/test-gen.d.ts +14 -0
- package/dist/commands/test-gen.js +52 -0
- package/dist/commands/triage.d.ts +70 -0
- package/dist/commands/triage.js +343 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +112 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +89 -0
- package/dist/commands/verify.d.ts +8 -0
- package/dist/commands/verify.js +65 -0
- package/dist/commands/worktree.d.ts +47 -0
- package/dist/commands/worktree.js +396 -0
- package/dist/core/benchmark.d.ts +57 -0
- package/dist/core/benchmark.js +772 -0
- package/dist/core/enforcement.d.ts +80 -0
- package/dist/core/enforcement.js +271 -0
- package/dist/core/legacy-templates.d.ts +24 -0
- package/dist/core/legacy-templates.js +60 -0
- package/dist/core/lock-manager.d.ts +54 -0
- package/dist/core/lock-manager.js +260 -0
- package/dist/core/root-resolver.d.ts +32 -0
- package/dist/core/root-resolver.js +149 -0
- package/dist/core/setup-assets.d.ts +123 -0
- package/dist/core/setup-assets.js +438 -0
- package/dist/core/telemetry.d.ts +156 -0
- package/dist/core/telemetry.js +416 -0
- package/dist/core/test-generator-types.d.ts +227 -0
- package/dist/core/test-generator-types.js +179 -0
- package/dist/core/test-generator.d.ts +34 -0
- package/dist/core/test-generator.js +1690 -0
- package/dist/core/tier1-liaison.d.ts +139 -0
- package/dist/core/tier1-liaison.js +320 -0
- package/dist/core/verifier.d.ts +52 -0
- package/dist/core/verifier.js +249 -0
- package/dist/core/version.d.ts +5 -0
- package/dist/core/version.js +14 -0
- package/dist/core/worktree-merge.d.ts +30 -0
- package/dist/core/worktree-merge.js +98 -0
- package/dist/db/data-engine.d.ts +46 -0
- package/dist/db/data-engine.js +802 -0
- package/dist/db/diff.d.ts +39 -0
- package/dist/db/diff.js +420 -0
- package/dist/db/env-parser.d.ts +86 -0
- package/dist/db/env-parser.js +549 -0
- package/dist/db/introspector.d.ts +21 -0
- package/dist/db/introspector.js +835 -0
- package/dist/db/types.d.ts +217 -0
- package/dist/db/types.js +9 -0
- package/dist/governor/circuit-breaker.d.ts +100 -0
- package/dist/governor/circuit-breaker.js +231 -0
- package/dist/governor/evaluator.d.ts +67 -0
- package/dist/governor/evaluator.js +851 -0
- package/dist/governor/index.d.ts +6 -0
- package/dist/governor/index.js +6 -0
- package/dist/governor/rules/api-rules.d.ts +2 -0
- package/dist/governor/rules/api-rules.js +140 -0
- package/dist/governor/rules/db-rules.d.ts +2 -0
- package/dist/governor/rules/db-rules.js +180 -0
- package/dist/governor/store.d.ts +33 -0
- package/dist/governor/store.js +84 -0
- package/dist/governor/types.d.ts +64 -0
- package/dist/governor/types.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +358 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +309 -0
- package/dist/providers/claude-api.d.ts +5 -0
- package/dist/providers/claude-api.js +75 -0
- package/dist/providers/claude-cli.d.ts +13 -0
- package/dist/providers/claude-cli.js +134 -0
- package/dist/providers/gemini.d.ts +8 -0
- package/dist/providers/gemini.js +78 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/registry.d.ts +38 -0
- package/dist/providers/registry.js +121 -0
- package/dist/providers/types.d.ts +74 -0
- package/dist/providers/types.js +25 -0
- package/dist/runner/agent-supervisor.d.ts +363 -0
- package/dist/runner/agent-supervisor.js +1672 -0
- package/dist/scanner/context-builder.d.ts +2 -0
- package/dist/scanner/context-builder.js +29 -0
- package/dist/scanner/detector.d.ts +2 -0
- package/dist/scanner/detector.js +760 -0
- package/dist/scanner/types.d.ts +80 -0
- package/dist/scanner/types.js +50 -0
- package/dist/server/studio-server.d.ts +39 -0
- package/dist/server/studio-server.js +1931 -0
- package/dist/server/studio-ui.d.ts +15 -0
- package/dist/server/studio-ui.js +5321 -0
- package/package.json +70 -0
- package/plugin/.claude-plugin/plugin.json +18 -0
- package/plugin/.mcp.json +10 -0
- package/plugin/agents/architect.md +28 -0
- package/plugin/agents/verifier.md +28 -0
- package/plugin/agents/worker.md +26 -0
- package/plugin/hooks/hooks.json +28 -0
- package/plugin/skills/nativ/SKILL.md +75 -0
- package/templates/.claudeignore +45 -0
- package/templates/AGENTS.md +67 -0
- package/templates/CLAUDE.md +32 -0
- package/templates/GEMINI.md +66 -0
- package/templates/claude-agents/architect.md +28 -0
- package/templates/claude-agents/verifier.md +28 -0
- package/templates/claude-agents/worker.md +26 -0
- package/templates/dot-ai/api_contracts.json +66 -0
- package/templates/dot-ai/context.md +38 -0
- package/templates/dot-ai/db_schema.json +39 -0
- package/templates/dot-ai/master_plan.json +28 -0
- package/templates/dot-ai/subagents/backend.md +11 -0
- package/templates/dot-ai/subagents/database.md +10 -0
- package/templates/dot-ai/subagents/db-migration.md +11 -0
- package/templates/dot-ai/subagents/devops-agent.md +11 -0
- package/templates/dot-ai/subagents/flutter-developer.md +11 -0
- package/templates/dot-ai/subagents/frontend.md +126 -0
- package/templates/dot-ai/subagents/qa-tester.md +13 -0
- package/templates/dot-ai/subagents/security-auditor.md +11 -0
- package/templates/dot-ai/ui_specs.md +78 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { withPlanLock, loadPlan } from './lock-manager.js';
|
|
6
|
+
import { ContractGovernor } from '../governor/index.js';
|
|
7
|
+
import { executeVerification } from './verifier.js';
|
|
8
|
+
import { loadTelemetry, recordTaskStart, recordTaskComplete, recordCircuitBreakerTrip, estimateTokens, } from './telemetry.js';
|
|
9
|
+
import { runTaskStart, runTaskComplete, runTaskAdd } from '../commands/task.js';
|
|
10
|
+
/**
|
|
11
|
+
* Creates an isolated scratch workspace for benchmarking with valid .ai/ contracts.
|
|
12
|
+
*/
|
|
13
|
+
export function createBenchmarkSandbox() {
|
|
14
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'nativ-bench-'));
|
|
15
|
+
const aiDir = path.join(dir, '.ai');
|
|
16
|
+
fs.mkdirSync(aiDir, { recursive: true });
|
|
17
|
+
fs.writeFileSync(path.join(aiDir, 'context.md'), '# Benchmark Project\nArchitecture: Node.js / TypeScript microservices with SQLite / PostgreSQL.\n', 'utf8');
|
|
18
|
+
const initialPlan = {
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
projectName: 'benchmark-sandbox',
|
|
21
|
+
overallStatus: 'in_progress',
|
|
22
|
+
activeMilestoneId: 'm1',
|
|
23
|
+
lastUpdated: new Date().toISOString(),
|
|
24
|
+
milestones: [
|
|
25
|
+
{
|
|
26
|
+
id: 'm1',
|
|
27
|
+
name: 'Core Infrastructure',
|
|
28
|
+
status: 'in_progress',
|
|
29
|
+
tasks: [
|
|
30
|
+
{
|
|
31
|
+
id: 'task-01',
|
|
32
|
+
title: 'Setup Database Connection',
|
|
33
|
+
description: 'Database setup',
|
|
34
|
+
assignedSubagent: 'database',
|
|
35
|
+
dependencies: [],
|
|
36
|
+
targetFiles: ['src/db.ts'],
|
|
37
|
+
status: 'pending',
|
|
38
|
+
verificationCommand: 'node -e "process.exit(0)"',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'task-02',
|
|
42
|
+
title: 'Implement Auth Routes',
|
|
43
|
+
description: 'Authentication routes',
|
|
44
|
+
assignedSubagent: 'backend',
|
|
45
|
+
dependencies: ['task-01'],
|
|
46
|
+
targetFiles: ['src/auth.ts'],
|
|
47
|
+
status: 'pending',
|
|
48
|
+
verificationCommand: 'node -e "process.exit(0)"',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'task-03',
|
|
52
|
+
title: 'Design Login Screen',
|
|
53
|
+
description: 'Login UI layout',
|
|
54
|
+
assignedSubagent: 'frontend',
|
|
55
|
+
dependencies: ['task-02'],
|
|
56
|
+
targetFiles: ['src/views/login.html'],
|
|
57
|
+
status: 'pending',
|
|
58
|
+
verificationCommand: 'none',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'm2',
|
|
64
|
+
name: 'API & Gateway',
|
|
65
|
+
status: 'pending',
|
|
66
|
+
tasks: [
|
|
67
|
+
{
|
|
68
|
+
id: 'task-04',
|
|
69
|
+
title: 'Build GraphQL Gateway',
|
|
70
|
+
description: 'Gateway service',
|
|
71
|
+
assignedSubagent: 'backend',
|
|
72
|
+
dependencies: ['task-02'],
|
|
73
|
+
targetFiles: ['src/gateway.ts'],
|
|
74
|
+
status: 'pending',
|
|
75
|
+
verificationCommand: 'node -e "process.exit(0)"',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
fs.writeFileSync(path.join(aiDir, 'master_plan.json'), JSON.stringify(initialPlan, null, 2), 'utf8');
|
|
82
|
+
const dbSchema = {
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
tables: [
|
|
85
|
+
{
|
|
86
|
+
name: 'users',
|
|
87
|
+
columns: [
|
|
88
|
+
{ name: 'id', type: 'VARCHAR(36)', nullable: false, primaryKey: true },
|
|
89
|
+
{ name: 'email', type: 'VARCHAR(255)', nullable: false, unique: true },
|
|
90
|
+
{ name: 'password_hash', type: 'VARCHAR(255)', nullable: false },
|
|
91
|
+
],
|
|
92
|
+
indexes: [{ name: 'idx_users_email', columns: ['email'], unique: true }],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
fs.writeFileSync(path.join(aiDir, 'db_schema.json'), JSON.stringify(dbSchema, null, 2), 'utf8');
|
|
97
|
+
const apiContracts = {
|
|
98
|
+
version: '1.0.0',
|
|
99
|
+
endpoints: [
|
|
100
|
+
{
|
|
101
|
+
id: 'post-auth-login',
|
|
102
|
+
path: '/api/auth/login',
|
|
103
|
+
method: 'POST',
|
|
104
|
+
summary: 'User login',
|
|
105
|
+
requestBody: {
|
|
106
|
+
contentType: 'application/json',
|
|
107
|
+
schema: { email: 'string', password: 'string' },
|
|
108
|
+
},
|
|
109
|
+
responses: {
|
|
110
|
+
'200': { description: 'Authenticated token', schema: { token: 'string' } },
|
|
111
|
+
'401': { description: 'Invalid credentials' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
fs.writeFileSync(path.join(aiDir, 'api_contracts.json'), JSON.stringify(apiContracts, null, 2), 'utf8');
|
|
117
|
+
return dir;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resilient sandbox cleanup handling Windows NTFS delayed locks.
|
|
121
|
+
*/
|
|
122
|
+
export function cleanupSandbox(dir) {
|
|
123
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
124
|
+
try {
|
|
125
|
+
if (fs.existsSync(dir)) {
|
|
126
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Executes a function quietly, keeping benchmark stdout clean for reports and JSON.
|
|
137
|
+
*/
|
|
138
|
+
async function silent(fn) {
|
|
139
|
+
const origLog = console.log;
|
|
140
|
+
const origErr = console.error;
|
|
141
|
+
const origInfo = console.info;
|
|
142
|
+
const origWarn = console.warn;
|
|
143
|
+
console.log = () => { };
|
|
144
|
+
console.error = () => { };
|
|
145
|
+
console.info = () => { };
|
|
146
|
+
console.warn = () => { };
|
|
147
|
+
try {
|
|
148
|
+
return await fn();
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
console.log = origLog;
|
|
152
|
+
console.error = origErr;
|
|
153
|
+
console.info = origInfo;
|
|
154
|
+
console.warn = origWarn;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Scenario 1: Multi-Agent Concurrency & Lock Contention
|
|
159
|
+
*/
|
|
160
|
+
async function runConcurrencyScenario(concurrency = 6) {
|
|
161
|
+
const sandbox = createBenchmarkSandbox();
|
|
162
|
+
const planPath = path.join(sandbox, '.ai', 'master_plan.json');
|
|
163
|
+
const errors = [];
|
|
164
|
+
let assertionsPassed = 0;
|
|
165
|
+
let assertionsFailed = 0;
|
|
166
|
+
const startTime = Date.now();
|
|
167
|
+
const lockWaitTimes = [];
|
|
168
|
+
try {
|
|
169
|
+
// Populate 20 tasks into milestone 1
|
|
170
|
+
await withPlanLock(planPath, (plan) => {
|
|
171
|
+
plan.milestones[0].tasks = [];
|
|
172
|
+
for (let i = 1; i <= 20; i++) {
|
|
173
|
+
const id = `bench-task-${String(i).padStart(2, '0')}`;
|
|
174
|
+
plan.milestones[0].tasks.push({
|
|
175
|
+
id,
|
|
176
|
+
title: `Synthetic Task ${i}`,
|
|
177
|
+
description: `Synthetic task ${i} description`,
|
|
178
|
+
assignedSubagent: i % 2 === 0 ? 'backend' : 'frontend',
|
|
179
|
+
dependencies: [],
|
|
180
|
+
targetFiles: [`file-${i}.ts`],
|
|
181
|
+
status: 'pending',
|
|
182
|
+
verificationCommand: 'node -e "process.exit(0)"',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
const totalOps = 20 + 20 + 8; // 20 starts, 20 completes, 8 concurrent adds
|
|
187
|
+
// 1. Parallel Task Starts
|
|
188
|
+
const startPromises = Array.from({ length: 20 }, async (_, i) => {
|
|
189
|
+
const taskId = `bench-task-${String(i + 1).padStart(2, '0')}`;
|
|
190
|
+
const t0 = Date.now();
|
|
191
|
+
await runTaskStart(taskId, sandbox);
|
|
192
|
+
lockWaitTimes.push(Date.now() - t0);
|
|
193
|
+
});
|
|
194
|
+
await Promise.all(startPromises);
|
|
195
|
+
// Verify all 20 in_progress
|
|
196
|
+
const p1 = loadPlan(planPath);
|
|
197
|
+
if (p1 && p1.milestones[0].tasks.every((t) => t.status === 'in_progress')) {
|
|
198
|
+
assertionsPassed++;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
assertionsFailed++;
|
|
202
|
+
errors.push('Not all tasks were atomically transitioned to in_progress.');
|
|
203
|
+
}
|
|
204
|
+
// 2. Parallel Task Completes
|
|
205
|
+
const completePromises = Array.from({ length: 20 }, async (_, i) => {
|
|
206
|
+
const taskId = `bench-task-${String(i + 1).padStart(2, '0')}`;
|
|
207
|
+
const t0 = Date.now();
|
|
208
|
+
await runTaskComplete(taskId, sandbox, { skipVerify: true });
|
|
209
|
+
lockWaitTimes.push(Date.now() - t0);
|
|
210
|
+
});
|
|
211
|
+
await Promise.all(completePromises);
|
|
212
|
+
// Verify all 20 completed
|
|
213
|
+
const p2 = loadPlan(planPath);
|
|
214
|
+
if (p2 && p2.milestones[0].tasks.every((t) => t.status === 'completed')) {
|
|
215
|
+
assertionsPassed++;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
assertionsFailed++;
|
|
219
|
+
errors.push('Not all tasks were atomically transitioned to completed.');
|
|
220
|
+
}
|
|
221
|
+
// 3. Parallel Task Adds
|
|
222
|
+
const addPromises = Array.from({ length: 8 }, async (_, i) => {
|
|
223
|
+
const t0 = Date.now();
|
|
224
|
+
await runTaskAdd(`Dynamic Worker Task ${i + 1}`, sandbox);
|
|
225
|
+
lockWaitTimes.push(Date.now() - t0);
|
|
226
|
+
});
|
|
227
|
+
await Promise.all(addPromises);
|
|
228
|
+
const p3 = loadPlan(planPath);
|
|
229
|
+
const totalTasks = p3 ? p3.milestones.reduce((acc, m) => acc + m.tasks.length, 0) : 0;
|
|
230
|
+
if (totalTasks === 29) {
|
|
231
|
+
assertionsPassed++;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
assertionsFailed++;
|
|
235
|
+
errors.push(`Expected 29 tasks across milestones after concurrent adds, found ${totalTasks}`);
|
|
236
|
+
}
|
|
237
|
+
const durationMs = Date.now() - startTime;
|
|
238
|
+
const meanLockWait = lockWaitTimes.length > 0
|
|
239
|
+
? Math.round((lockWaitTimes.reduce((a, b) => a + b, 0) / lockWaitTimes.length) * 100) / 100
|
|
240
|
+
: 0;
|
|
241
|
+
const maxLockWait = lockWaitTimes.length > 0 ? Math.max(...lockWaitTimes) : 0;
|
|
242
|
+
const throughput = Math.round((totalOps / (durationMs / 1000)) * 10) / 10;
|
|
243
|
+
return {
|
|
244
|
+
id: 'concurrency',
|
|
245
|
+
name: 'Multi-Agent Lock Contention & Throughput',
|
|
246
|
+
description: 'Stresses atomic OS advisory locks and state transitions across concurrent agent workers.',
|
|
247
|
+
status: assertionsFailed === 0 ? 'passed' : 'failed',
|
|
248
|
+
durationMs,
|
|
249
|
+
operations: totalOps,
|
|
250
|
+
throughputOpsPerSec: throughput,
|
|
251
|
+
assertionsPassed,
|
|
252
|
+
assertionsFailed,
|
|
253
|
+
details: {
|
|
254
|
+
concurrentWorkers: concurrency,
|
|
255
|
+
totalAtomicMutations: totalOps,
|
|
256
|
+
meanLockWaitMs: meanLockWait,
|
|
257
|
+
maxLockWaitMs: maxLockWait,
|
|
258
|
+
},
|
|
259
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
cleanupSandbox(sandbox);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Scenario 2: Contract Governor Invariant Engine & Circuit Breaker
|
|
268
|
+
*/
|
|
269
|
+
async function runGovernorScenario() {
|
|
270
|
+
const sandbox = createBenchmarkSandbox();
|
|
271
|
+
const errors = [];
|
|
272
|
+
let assertionsPassed = 0;
|
|
273
|
+
let assertionsFailed = 0;
|
|
274
|
+
const startTime = Date.now();
|
|
275
|
+
let operations = 0;
|
|
276
|
+
try {
|
|
277
|
+
// 1. Additive DB change (Valid -> should approve)
|
|
278
|
+
operations++;
|
|
279
|
+
const addColPatch = {
|
|
280
|
+
taskId: 'bench-task-gov',
|
|
281
|
+
target: 'db_schema',
|
|
282
|
+
operation: 'ADD',
|
|
283
|
+
path: 'users.columns.bio',
|
|
284
|
+
value: { name: 'bio', type: 'TEXT', nullable: true },
|
|
285
|
+
reason: 'Add user bio field',
|
|
286
|
+
};
|
|
287
|
+
const res1 = ContractGovernor.evaluate(sandbox, addColPatch);
|
|
288
|
+
if (res1.approved && res1.patchApplied) {
|
|
289
|
+
assertionsPassed++;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
assertionsFailed++;
|
|
293
|
+
errors.push(`Additive DB change was rejected: ${res1.message}`);
|
|
294
|
+
}
|
|
295
|
+
// 2. Destructive DB change (Drop column -> must reject)
|
|
296
|
+
operations++;
|
|
297
|
+
const dropColPatch = {
|
|
298
|
+
taskId: 'bench-task-gov',
|
|
299
|
+
target: 'db_schema',
|
|
300
|
+
operation: 'DROP',
|
|
301
|
+
path: 'users.columns.email',
|
|
302
|
+
reason: 'Drop email column',
|
|
303
|
+
};
|
|
304
|
+
const res2 = ContractGovernor.evaluate(sandbox, dropColPatch);
|
|
305
|
+
if (!res2.approved && (res2.ruleId === 'DB_DROP_FORBIDDEN' || res2.blastRadius === 'HIGH_DESTRUCTIVE')) {
|
|
306
|
+
assertionsPassed++;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
assertionsFailed++;
|
|
310
|
+
errors.push('Destructive DB drop column was unexpectedly approved.');
|
|
311
|
+
}
|
|
312
|
+
// 3. Additive API change (Valid -> should approve)
|
|
313
|
+
operations++;
|
|
314
|
+
const addEndpointPatch = {
|
|
315
|
+
taskId: 'bench-task-gov',
|
|
316
|
+
target: 'api_contracts',
|
|
317
|
+
operation: 'ADD',
|
|
318
|
+
path: 'endpoints./api/users/profile',
|
|
319
|
+
value: {
|
|
320
|
+
id: 'get-users-profile',
|
|
321
|
+
path: '/api/users/profile',
|
|
322
|
+
method: 'GET',
|
|
323
|
+
summary: 'Get user profile',
|
|
324
|
+
},
|
|
325
|
+
reason: 'Expose profile query',
|
|
326
|
+
};
|
|
327
|
+
const res3 = ContractGovernor.evaluate(sandbox, addEndpointPatch);
|
|
328
|
+
if (res3.approved && res3.patchApplied) {
|
|
329
|
+
assertionsPassed++;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
assertionsFailed++;
|
|
333
|
+
errors.push(`Additive API change was rejected: ${res3.message}`);
|
|
334
|
+
}
|
|
335
|
+
// 4. Destructive API change (Drop endpoint -> must reject)
|
|
336
|
+
operations++;
|
|
337
|
+
const dropEndpointPatch = {
|
|
338
|
+
taskId: 'bench-task-gov',
|
|
339
|
+
target: 'api_contracts',
|
|
340
|
+
operation: 'DROP',
|
|
341
|
+
path: 'endpoints.post-auth-login',
|
|
342
|
+
reason: 'Drop login route',
|
|
343
|
+
};
|
|
344
|
+
const res4 = ContractGovernor.evaluate(sandbox, dropEndpointPatch);
|
|
345
|
+
if (!res4.approved && (res4.ruleId === 'API_DROP_FORBIDDEN' || res4.blastRadius === 'HIGH_DESTRUCTIVE')) {
|
|
346
|
+
assertionsPassed++;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
assertionsFailed++;
|
|
350
|
+
errors.push('Destructive endpoint deletion was unexpectedly approved.');
|
|
351
|
+
}
|
|
352
|
+
// 5. 3-Strike Circuit Breaker Tripping
|
|
353
|
+
operations += 3;
|
|
354
|
+
let tripped = false;
|
|
355
|
+
for (let i = 0; i < 3; i++) {
|
|
356
|
+
const dropAttempt = {
|
|
357
|
+
taskId: 'bench-task-runaway',
|
|
358
|
+
target: 'db_schema',
|
|
359
|
+
operation: 'DROP',
|
|
360
|
+
path: 'users.columns.id',
|
|
361
|
+
reason: `Repeated destructive drop attempt ${i + 1}`,
|
|
362
|
+
};
|
|
363
|
+
const r = ContractGovernor.evaluate(sandbox, dropAttempt);
|
|
364
|
+
if (r.circuitBreaker?.tripped) {
|
|
365
|
+
tripped = true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (tripped) {
|
|
369
|
+
assertionsPassed++;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
assertionsFailed++;
|
|
373
|
+
errors.push('Governor circuit breaker did not trip after 3 consecutive destructive violations.');
|
|
374
|
+
}
|
|
375
|
+
const escalationPath = path.join(sandbox, '.ai', 'escalation.json');
|
|
376
|
+
if (fs.existsSync(escalationPath)) {
|
|
377
|
+
const esc = JSON.parse(fs.readFileSync(escalationPath, 'utf8'));
|
|
378
|
+
if (esc.escalations.length > 0) {
|
|
379
|
+
assertionsPassed++;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
assertionsFailed++;
|
|
383
|
+
errors.push('Escalation record was not logged by circuit breaker flight recorder.');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
assertionsFailed++;
|
|
388
|
+
errors.push('No .ai/escalation.json was created upon circuit breaker trip.');
|
|
389
|
+
}
|
|
390
|
+
const durationMs = Date.now() - startTime;
|
|
391
|
+
const throughput = Math.round((operations / (durationMs / 1000)) * 10) / 10;
|
|
392
|
+
return {
|
|
393
|
+
id: 'governor',
|
|
394
|
+
name: 'Contract Invariant & Circuit Breaker Engine',
|
|
395
|
+
description: 'Tests invariant evaluation, additive auto-approvals, destructive rejections, and 3-strike escalation.',
|
|
396
|
+
status: assertionsFailed === 0 ? 'passed' : 'failed',
|
|
397
|
+
durationMs,
|
|
398
|
+
operations,
|
|
399
|
+
throughputOpsPerSec: throughput,
|
|
400
|
+
assertionsPassed,
|
|
401
|
+
assertionsFailed,
|
|
402
|
+
details: {
|
|
403
|
+
invariantsChecked: ['NO_DESTRUCTIVE_SCHEMA', 'NO_ENDPOINT_DELETION', 'ADDITIVE_SCHEMA_APPROVAL'],
|
|
404
|
+
circuitBreakerTripped: tripped,
|
|
405
|
+
},
|
|
406
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
cleanupSandbox(sandbox);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Scenario 3: Verification Gatekeeper Performance & Isolation
|
|
415
|
+
*/
|
|
416
|
+
async function runVerificationScenario() {
|
|
417
|
+
const sandbox = createBenchmarkSandbox();
|
|
418
|
+
const errors = [];
|
|
419
|
+
let assertionsPassed = 0;
|
|
420
|
+
let assertionsFailed = 0;
|
|
421
|
+
const startTime = Date.now();
|
|
422
|
+
let operations = 0;
|
|
423
|
+
try {
|
|
424
|
+
// 1. Passing verification
|
|
425
|
+
operations++;
|
|
426
|
+
const passRes = await executeVerification('node -e "process.exit(0)"', sandbox);
|
|
427
|
+
if (passRes.success && passRes.exitCode === 0) {
|
|
428
|
+
assertionsPassed++;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
assertionsFailed++;
|
|
432
|
+
errors.push(`Passing verification failed with exit code ${passRes.exitCode}`);
|
|
433
|
+
}
|
|
434
|
+
// 2. Failing verification with custom exit code
|
|
435
|
+
operations++;
|
|
436
|
+
const failRes = await executeVerification('node -e "console.error(\\"synthetic error\\"); process.exit(42)"', sandbox);
|
|
437
|
+
if (!failRes.success && failRes.exitCode === 42 && failRes.stderr.includes('synthetic error')) {
|
|
438
|
+
assertionsPassed++;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
assertionsFailed++;
|
|
442
|
+
errors.push('Failing verification did not capture exit code 42 or stderr correctly.');
|
|
443
|
+
}
|
|
444
|
+
// 3. Skipped verification command
|
|
445
|
+
operations++;
|
|
446
|
+
const skipRes = await executeVerification('none', sandbox);
|
|
447
|
+
if (skipRes.success && skipRes.skipped) {
|
|
448
|
+
assertionsPassed++;
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
assertionsFailed++;
|
|
452
|
+
errors.push('Verification command "none" was not recognized as skipped.');
|
|
453
|
+
}
|
|
454
|
+
// 4. Timeout handling (150ms timeout on a command sleeping 500ms)
|
|
455
|
+
operations++;
|
|
456
|
+
const timeoutRes = await executeVerification('node -e "setTimeout(() => {}, 500)"', sandbox, 150);
|
|
457
|
+
if (!timeoutRes.success && (timeoutRes.exitCode === 124 || timeoutRes.exitCode === 1 || timeoutRes.durationMs >= 140)) {
|
|
458
|
+
assertionsPassed++;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
assertionsFailed++;
|
|
462
|
+
errors.push(`Timeout command did not fail as expected (exitCode ${timeoutRes.exitCode}, success: ${timeoutRes.success})`);
|
|
463
|
+
}
|
|
464
|
+
const durationMs = Date.now() - startTime;
|
|
465
|
+
const throughput = Math.round((operations / (durationMs / 1000)) * 10) / 10;
|
|
466
|
+
return {
|
|
467
|
+
id: 'verification',
|
|
468
|
+
name: 'Verification Gatekeeper & Regression Guard',
|
|
469
|
+
description: 'Tests test command execution, error diagnostics capture, timeout isolation, and skip handling.',
|
|
470
|
+
status: assertionsFailed === 0 ? 'passed' : 'failed',
|
|
471
|
+
durationMs,
|
|
472
|
+
operations,
|
|
473
|
+
throughputOpsPerSec: throughput,
|
|
474
|
+
assertionsPassed,
|
|
475
|
+
assertionsFailed,
|
|
476
|
+
details: {
|
|
477
|
+
testsExecuted: operations,
|
|
478
|
+
},
|
|
479
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
cleanupSandbox(sandbox);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Scenario 4: Telemetry & Cost Accounting Integrity
|
|
488
|
+
*/
|
|
489
|
+
async function runTelemetryScenario() {
|
|
490
|
+
const sandbox = createBenchmarkSandbox();
|
|
491
|
+
const errors = [];
|
|
492
|
+
let assertionsPassed = 0;
|
|
493
|
+
let assertionsFailed = 0;
|
|
494
|
+
const startTime = Date.now();
|
|
495
|
+
let operations = 0;
|
|
496
|
+
try {
|
|
497
|
+
const telemetryPath = path.join(sandbox, '.ai', 'telemetry.json');
|
|
498
|
+
// Create synthetic target files for token weight calculation
|
|
499
|
+
fs.writeFileSync(path.join(sandbox, 'service.ts'), 'export function execute() { return true; }\n'.repeat(40), 'utf8');
|
|
500
|
+
const syntheticTask = {
|
|
501
|
+
id: 'telemetry-task-01',
|
|
502
|
+
title: 'Implement Core Service',
|
|
503
|
+
description: 'Core service implementation',
|
|
504
|
+
assignedSubagent: 'backend',
|
|
505
|
+
dependencies: [],
|
|
506
|
+
targetFiles: ['service.ts'],
|
|
507
|
+
status: 'pending',
|
|
508
|
+
verificationCommand: 'npm test',
|
|
509
|
+
};
|
|
510
|
+
// 1. Estimate tokens
|
|
511
|
+
operations++;
|
|
512
|
+
const tokens = estimateTokens(sandbox, syntheticTask);
|
|
513
|
+
if (tokens.inputEstimated > 3500 && tokens.costUsdEstimated > 0) {
|
|
514
|
+
assertionsPassed++;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
assertionsFailed++;
|
|
518
|
+
errors.push('Token estimation heuristics failed to compute input tokens or cost.');
|
|
519
|
+
}
|
|
520
|
+
// 2. Record task start
|
|
521
|
+
operations++;
|
|
522
|
+
await recordTaskStart(sandbox, syntheticTask);
|
|
523
|
+
let tel = loadTelemetry(telemetryPath, 'bench-proj');
|
|
524
|
+
if (tel.tasks.length === 1 && tel.tasks[0].status === 'in_progress') {
|
|
525
|
+
assertionsPassed++;
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
assertionsFailed++;
|
|
529
|
+
errors.push('Telemetry recordTaskStart did not register in_progress record.');
|
|
530
|
+
}
|
|
531
|
+
// 3. Record task complete with verification
|
|
532
|
+
operations++;
|
|
533
|
+
await recordTaskComplete(sandbox, syntheticTask, {
|
|
534
|
+
command: 'npm test',
|
|
535
|
+
durationMs: 45,
|
|
536
|
+
exitCode: 0,
|
|
537
|
+
stdout: 'All tests passed',
|
|
538
|
+
stderr: '',
|
|
539
|
+
skipped: false,
|
|
540
|
+
success: true,
|
|
541
|
+
});
|
|
542
|
+
tel = loadTelemetry(telemetryPath, 'bench-proj');
|
|
543
|
+
if (tel.summary.totalTasksCompleted === 1 &&
|
|
544
|
+
tel.summary.totalVerificationsRun === 1 &&
|
|
545
|
+
tel.summary.verificationPassRate === 1.0 &&
|
|
546
|
+
tel.summary.estimatedTotalCostUsd > 0) {
|
|
547
|
+
assertionsPassed++;
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
assertionsFailed++;
|
|
551
|
+
errors.push('Telemetry recordTaskComplete failed to compute summary metrics correctly.');
|
|
552
|
+
}
|
|
553
|
+
// 4. Circuit breaker trip recording
|
|
554
|
+
operations++;
|
|
555
|
+
await recordCircuitBreakerTrip(sandbox, 'telemetry-task-01');
|
|
556
|
+
tel = loadTelemetry(telemetryPath, 'bench-proj');
|
|
557
|
+
if (tel.summary.circuitBreakerTrips === 1) {
|
|
558
|
+
assertionsPassed++;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
assertionsFailed++;
|
|
562
|
+
errors.push('Telemetry recordCircuitBreakerTrip did not increment trip counter.');
|
|
563
|
+
}
|
|
564
|
+
const durationMs = Date.now() - startTime;
|
|
565
|
+
const throughput = Math.round((operations / (durationMs / 1000)) * 10) / 10;
|
|
566
|
+
return {
|
|
567
|
+
id: 'telemetry',
|
|
568
|
+
name: 'Telemetry & Cost Accounting Integrity',
|
|
569
|
+
description: 'Tests live recording of durations, token heuristics, cost estimates, and pass rates.',
|
|
570
|
+
status: assertionsFailed === 0 ? 'passed' : 'failed',
|
|
571
|
+
durationMs,
|
|
572
|
+
operations,
|
|
573
|
+
throughputOpsPerSec: throughput,
|
|
574
|
+
assertionsPassed,
|
|
575
|
+
assertionsFailed,
|
|
576
|
+
details: {
|
|
577
|
+
totalCostUsd: tel.summary.estimatedTotalCostUsd,
|
|
578
|
+
totalTokens: tel.summary.estimatedTotalTokens,
|
|
579
|
+
circuitBreakersRecorded: tel.summary.circuitBreakerTrips,
|
|
580
|
+
},
|
|
581
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
finally {
|
|
585
|
+
cleanupSandbox(sandbox);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Scenario 5: End-to-End Autonomous Multi-Agent Pipeline
|
|
590
|
+
*/
|
|
591
|
+
async function runE2EPipelineScenario() {
|
|
592
|
+
const sandbox = createBenchmarkSandbox();
|
|
593
|
+
const errors = [];
|
|
594
|
+
let assertionsPassed = 0;
|
|
595
|
+
let assertionsFailed = 0;
|
|
596
|
+
const startTime = Date.now();
|
|
597
|
+
let operations = 0;
|
|
598
|
+
try {
|
|
599
|
+
const planPath = path.join(sandbox, '.ai', 'master_plan.json');
|
|
600
|
+
// 1. Task 1 lifecycle
|
|
601
|
+
operations++;
|
|
602
|
+
await runTaskStart('task-01', sandbox);
|
|
603
|
+
await runTaskComplete('task-01', sandbox);
|
|
604
|
+
// 2. Task 2 lifecycle
|
|
605
|
+
operations++;
|
|
606
|
+
await runTaskStart('task-02', sandbox);
|
|
607
|
+
await runTaskComplete('task-02', sandbox);
|
|
608
|
+
// 3. Task 3 lifecycle (completes Milestone 1)
|
|
609
|
+
operations++;
|
|
610
|
+
await runTaskStart('task-03', sandbox);
|
|
611
|
+
await runTaskComplete('task-03', sandbox);
|
|
612
|
+
// Check if Milestone 1 completed and active milestone advanced to m2
|
|
613
|
+
const planAfterM1 = loadPlan(planPath);
|
|
614
|
+
if (planAfterM1 &&
|
|
615
|
+
planAfterM1.milestones[0].status === 'completed' &&
|
|
616
|
+
planAfterM1.activeMilestoneId === 'm2') {
|
|
617
|
+
assertionsPassed++;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
assertionsFailed++;
|
|
621
|
+
errors.push(`Active milestone failed to advance to m2. Current: ${planAfterM1?.activeMilestoneId}`);
|
|
622
|
+
}
|
|
623
|
+
// 4. Task 4 lifecycle (completes Milestone 2 & overall project)
|
|
624
|
+
operations++;
|
|
625
|
+
await runTaskStart('task-04', sandbox);
|
|
626
|
+
await runTaskComplete('task-04', sandbox);
|
|
627
|
+
const finalPlan = loadPlan(planPath);
|
|
628
|
+
if (finalPlan &&
|
|
629
|
+
finalPlan.milestones[1].status === 'completed' &&
|
|
630
|
+
finalPlan.overallStatus === 'completed') {
|
|
631
|
+
assertionsPassed++;
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
assertionsFailed++;
|
|
635
|
+
errors.push(`Overall project status failed to mark completed. Status: ${finalPlan?.overallStatus}`);
|
|
636
|
+
}
|
|
637
|
+
const durationMs = Date.now() - startTime;
|
|
638
|
+
const throughput = Math.round((operations / (durationMs / 1000)) * 10) / 10;
|
|
639
|
+
return {
|
|
640
|
+
id: 'e2e_pipeline',
|
|
641
|
+
name: 'Autonomous Multi-Agent Lifecycle & Milestone Graph',
|
|
642
|
+
description: 'Simulates complete multi-agent lifecycle across dependency graph and milestone advancement.',
|
|
643
|
+
status: assertionsFailed === 0 ? 'passed' : 'failed',
|
|
644
|
+
durationMs,
|
|
645
|
+
operations,
|
|
646
|
+
throughputOpsPerSec: throughput,
|
|
647
|
+
assertionsPassed,
|
|
648
|
+
assertionsFailed,
|
|
649
|
+
details: {
|
|
650
|
+
milestonesResolved: 2,
|
|
651
|
+
tasksCompleted: 4,
|
|
652
|
+
projectStatus: finalPlan?.overallStatus || 'unknown',
|
|
653
|
+
},
|
|
654
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
finally {
|
|
658
|
+
cleanupSandbox(sandbox);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Main benchmark execution coordinator.
|
|
663
|
+
*/
|
|
664
|
+
export async function runBenchmarks(options = {}) {
|
|
665
|
+
const scenarioFilter = options.scenario ? options.scenario.toLowerCase().trim() : 'all';
|
|
666
|
+
const concurrency = options.concurrency || 6;
|
|
667
|
+
const scenariosToRun = [];
|
|
668
|
+
if (scenarioFilter === 'all' || scenarioFilter === 'concurrency') {
|
|
669
|
+
scenariosToRun.push(() => runConcurrencyScenario(concurrency));
|
|
670
|
+
}
|
|
671
|
+
if (scenarioFilter === 'all' || scenarioFilter === 'governor') {
|
|
672
|
+
scenariosToRun.push(() => runGovernorScenario());
|
|
673
|
+
}
|
|
674
|
+
if (scenarioFilter === 'all' || scenarioFilter === 'verification') {
|
|
675
|
+
scenariosToRun.push(() => runVerificationScenario());
|
|
676
|
+
}
|
|
677
|
+
if (scenarioFilter === 'all' || scenarioFilter === 'telemetry') {
|
|
678
|
+
scenariosToRun.push(() => runTelemetryScenario());
|
|
679
|
+
}
|
|
680
|
+
if (scenarioFilter === 'all' || scenarioFilter === 'e2e' || scenarioFilter === 'e2e_pipeline') {
|
|
681
|
+
scenariosToRun.push(() => runE2EPipelineScenario());
|
|
682
|
+
}
|
|
683
|
+
const results = [];
|
|
684
|
+
const overallStart = Date.now();
|
|
685
|
+
for (const runner of scenariosToRun) {
|
|
686
|
+
const res = await silent(() => runner());
|
|
687
|
+
results.push(res);
|
|
688
|
+
}
|
|
689
|
+
const totalDurationMs = Date.now() - overallStart;
|
|
690
|
+
const passedCount = results.filter((r) => r.status === 'passed').length;
|
|
691
|
+
const totalOps = results.reduce((acc, r) => acc + r.operations, 0);
|
|
692
|
+
const avgThroughput = results.length > 0
|
|
693
|
+
? Math.round((results.reduce((acc, r) => acc + r.throughputOpsPerSec, 0) / results.length) * 10) / 10
|
|
694
|
+
: 0;
|
|
695
|
+
const score = results.length > 0 ? Math.round((passedCount / results.length) * 100) : 0;
|
|
696
|
+
const report = {
|
|
697
|
+
version: '1.0.0',
|
|
698
|
+
timestamp: new Date().toISOString(),
|
|
699
|
+
environment: {
|
|
700
|
+
platform: process.platform,
|
|
701
|
+
nodeVersion: process.version,
|
|
702
|
+
cpuCount: os.cpus().length,
|
|
703
|
+
arch: process.arch,
|
|
704
|
+
},
|
|
705
|
+
summary: {
|
|
706
|
+
totalScenarios: results.length,
|
|
707
|
+
passedScenarios: passedCount,
|
|
708
|
+
failedScenarios: results.length - passedCount,
|
|
709
|
+
score,
|
|
710
|
+
totalDurationMs,
|
|
711
|
+
totalOperations: totalOps,
|
|
712
|
+
averageThroughputOpsPerSec: avgThroughput,
|
|
713
|
+
},
|
|
714
|
+
scenarios: results,
|
|
715
|
+
};
|
|
716
|
+
return report;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Formats the benchmark report for high-readability CLI output.
|
|
720
|
+
*/
|
|
721
|
+
export function formatBenchmarkReport(report) {
|
|
722
|
+
const lines = [];
|
|
723
|
+
lines.push(pc.bold(pc.cyan('\n============================================================')));
|
|
724
|
+
lines.push(pc.bold(pc.cyan(' NATIV LOCAL SYNTHETIC BENCHMARK SUITE ')));
|
|
725
|
+
lines.push(pc.bold(pc.cyan('============================================================')));
|
|
726
|
+
lines.push(pc.dim('Environment: ') +
|
|
727
|
+
pc.white(`${report.environment.platform} (${report.environment.arch})`) +
|
|
728
|
+
pc.dim(' | Node: ') +
|
|
729
|
+
pc.white(report.environment.nodeVersion) +
|
|
730
|
+
pc.dim(' | CPUs: ') +
|
|
731
|
+
pc.white(`${report.environment.cpuCount}`));
|
|
732
|
+
lines.push(pc.dim('Timestamp: ') + pc.white(report.timestamp));
|
|
733
|
+
lines.push('');
|
|
734
|
+
lines.push(pc.bold('Benchmark Scenarios:'));
|
|
735
|
+
for (const s of report.scenarios) {
|
|
736
|
+
const badge = s.status === 'passed' ? pc.green('[PASS]') : pc.red('[FAIL]');
|
|
737
|
+
const dur = (s.durationMs / 1000).toFixed(2);
|
|
738
|
+
lines.push(` ${badge} ${pc.bold(s.name)}`);
|
|
739
|
+
lines.push(pc.dim(` Throughput: `) +
|
|
740
|
+
pc.yellow(`${s.throughputOpsPerSec} ops/sec`) +
|
|
741
|
+
pc.dim(` (${s.operations} ops in ${dur}s)`));
|
|
742
|
+
lines.push(pc.dim(` Assertions: `) +
|
|
743
|
+
pc.green(`${s.assertionsPassed} passed`) +
|
|
744
|
+
(s.assertionsFailed > 0 ? pc.red(`, ${s.assertionsFailed} failed`) : ''));
|
|
745
|
+
if (s.details.meanLockWaitMs !== undefined) {
|
|
746
|
+
lines.push(pc.dim(` Lock Wait: `) +
|
|
747
|
+
pc.white(`mean ${s.details.meanLockWaitMs}ms | max ${s.details.maxLockWaitMs}ms`));
|
|
748
|
+
}
|
|
749
|
+
if (s.details.totalCostUsd !== undefined) {
|
|
750
|
+
lines.push(pc.dim(` Telemetry: `) +
|
|
751
|
+
pc.white(`~$${s.details.totalCostUsd} USD (${s.details.totalTokens} tokens)`));
|
|
752
|
+
}
|
|
753
|
+
if (s.errors && s.errors.length > 0) {
|
|
754
|
+
for (const err of s.errors) {
|
|
755
|
+
lines.push(pc.red(` ✖ Error: ${err}`));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
lines.push('');
|
|
759
|
+
}
|
|
760
|
+
lines.push(pc.bold(pc.cyan('------------------------------------------------------------')));
|
|
761
|
+
const scoreColor = report.summary.score === 100 ? pc.green : report.summary.score >= 80 ? pc.yellow : pc.red;
|
|
762
|
+
lines.push(pc.bold('Benchmark Score: ') +
|
|
763
|
+
scoreColor(pc.bold(`${report.summary.score}%`)) +
|
|
764
|
+
pc.dim(` (${report.summary.passedScenarios}/${report.summary.totalScenarios} scenarios passed)`));
|
|
765
|
+
lines.push(pc.bold('Total Operations: ') +
|
|
766
|
+
pc.white(`${report.summary.totalOperations}`) +
|
|
767
|
+
pc.dim(` (${report.summary.averageThroughputOpsPerSec} avg ops/sec)`));
|
|
768
|
+
lines.push(pc.bold('Total Elapsed Time: ') +
|
|
769
|
+
pc.white(`${(report.summary.totalDurationMs / 1000).toFixed(2)}s (${report.summary.totalDurationMs}ms)`));
|
|
770
|
+
lines.push(pc.bold(pc.cyan('============================================================\n')));
|
|
771
|
+
return lines.join('\n');
|
|
772
|
+
}
|