@veraxhq/verax 0.2.1 → 0.4.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/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 6A: Transactional Artifact System
|
|
3
|
+
*
|
|
4
|
+
* Provides ALL-OR-NOTHING artifact writes with staging directory.
|
|
5
|
+
* Ensures atomic finalization of complete artifact sets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, renameSync, existsSync, rmSync, readdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create staging directory for transactional writes
|
|
13
|
+
*
|
|
14
|
+
* @param {string} runDir - Run directory path
|
|
15
|
+
* @returns {{ ok: boolean, stagingDir?: string, error?: Error }} Result
|
|
16
|
+
*/
|
|
17
|
+
export function createStagingDir(runDir) {
|
|
18
|
+
try {
|
|
19
|
+
const stagingDir = join(runDir, '.staging');
|
|
20
|
+
|
|
21
|
+
// Clean up any existing staging directory from previous crash
|
|
22
|
+
if (existsSync(stagingDir)) {
|
|
23
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
return { ok: true, stagingDir };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return { ok: false, error };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Commit staging directory to final location (atomic rename)
|
|
36
|
+
*
|
|
37
|
+
* @param {string} runDir - Run directory path
|
|
38
|
+
* @returns {{ ok: boolean, error?: Error }} Result
|
|
39
|
+
*/
|
|
40
|
+
export function commitStagingDir(runDir) {
|
|
41
|
+
try {
|
|
42
|
+
const stagingDir = join(runDir, '.staging');
|
|
43
|
+
const artifactsDir = join(runDir, 'artifacts');
|
|
44
|
+
|
|
45
|
+
if (!existsSync(stagingDir)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: new Error('Staging directory does not exist'),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Ensure parent directory exists
|
|
53
|
+
mkdirSync(runDir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
// Clean up existing artifacts directory if present
|
|
56
|
+
if (existsSync(artifactsDir)) {
|
|
57
|
+
rmSync(artifactsDir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Atomic rename
|
|
61
|
+
renameSync(stagingDir, artifactsDir);
|
|
62
|
+
|
|
63
|
+
return { ok: true };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return { ok: false, error };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Rollback staging directory (cleanup on failure)
|
|
71
|
+
*
|
|
72
|
+
* @param {string} runDir - Run directory path
|
|
73
|
+
* @returns {{ ok: boolean, error?: Error }} Result
|
|
74
|
+
*/
|
|
75
|
+
export function rollbackStagingDir(runDir) {
|
|
76
|
+
try {
|
|
77
|
+
const stagingDir = join(runDir, '.staging');
|
|
78
|
+
|
|
79
|
+
if (existsSync(stagingDir)) {
|
|
80
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { ok: true };
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { ok: false, error };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get staging artifact path
|
|
91
|
+
*
|
|
92
|
+
* @param {string} runDir - Run directory path
|
|
93
|
+
* @param {string} artifactName - Artifact filename
|
|
94
|
+
* @returns {string} Path to artifact in staging directory
|
|
95
|
+
*/
|
|
96
|
+
export function getStagingPath(runDir, artifactName) {
|
|
97
|
+
return join(runDir, '.staging', artifactName);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get final artifact path
|
|
102
|
+
*
|
|
103
|
+
* @param {string} runDir - Run directory path
|
|
104
|
+
* @param {string} artifactName - Artifact filename
|
|
105
|
+
* @returns {string} Path to artifact in final location
|
|
106
|
+
*/
|
|
107
|
+
export function getFinalPath(runDir, artifactName) {
|
|
108
|
+
return join(runDir, 'artifacts', artifactName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if staging directory exists
|
|
113
|
+
*
|
|
114
|
+
* @param {string} runDir - Run directory path
|
|
115
|
+
* @returns {boolean} True if staging exists
|
|
116
|
+
*/
|
|
117
|
+
export function hasStagingDir(runDir) {
|
|
118
|
+
const stagingDir = join(runDir, '.staging');
|
|
119
|
+
return existsSync(stagingDir);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* List files in staging directory
|
|
124
|
+
*
|
|
125
|
+
* @param {string} runDir - Run directory path
|
|
126
|
+
* @returns {string[]} List of filenames in staging
|
|
127
|
+
*/
|
|
128
|
+
export function listStagingFiles(runDir) {
|
|
129
|
+
try {
|
|
130
|
+
const stagingDir = join(runDir, '.staging');
|
|
131
|
+
|
|
132
|
+
if (!existsSync(stagingDir)) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return readdirSync(stagingDir);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.10 — Unified Run Timeline
|
|
3
|
+
*
|
|
4
|
+
* Builds chronological timeline of all events in a run.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load artifact JSON
|
|
12
|
+
*/
|
|
13
|
+
function loadArtifact(runDir, filename) {
|
|
14
|
+
const path = resolve(runDir, filename);
|
|
15
|
+
if (!existsSync(path)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
20
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build timeline from run artifacts
|
|
28
|
+
*
|
|
29
|
+
* @param {string} projectDir - Project directory
|
|
30
|
+
* @param {string} runId - Run ID
|
|
31
|
+
* @returns {Object} Timeline object
|
|
32
|
+
*/
|
|
33
|
+
export function buildRunTimeline(projectDir, runId) {
|
|
34
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
35
|
+
|
|
36
|
+
if (!existsSync(runDir)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const timeline = [];
|
|
41
|
+
|
|
42
|
+
// Load summary for phase timestamps
|
|
43
|
+
const summary = loadArtifact(runDir, 'summary.json');
|
|
44
|
+
const runStatus = loadArtifact(runDir, 'run.status.json');
|
|
45
|
+
const decisions = loadArtifact(runDir, 'decisions.json');
|
|
46
|
+
const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
|
|
47
|
+
const performanceReport = loadArtifact(runDir, 'performance.report.json');
|
|
48
|
+
const findings = loadArtifact(runDir, 'findings.json');
|
|
49
|
+
|
|
50
|
+
// Extract timestamps
|
|
51
|
+
const startedAt = summary?.startedAt || runStatus?.startedAt;
|
|
52
|
+
const completedAt = summary?.completedAt || runStatus?.completedAt;
|
|
53
|
+
|
|
54
|
+
// Phase events
|
|
55
|
+
if (startedAt) {
|
|
56
|
+
timeline.push({
|
|
57
|
+
timestamp: startedAt,
|
|
58
|
+
phase: 'RUN_START',
|
|
59
|
+
event: 'run_started',
|
|
60
|
+
data: {
|
|
61
|
+
runId,
|
|
62
|
+
url: summary?.url || null
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// LEARN phase
|
|
68
|
+
if (summary?.metrics?.learnMs) {
|
|
69
|
+
timeline.push({
|
|
70
|
+
timestamp: startedAt || null,
|
|
71
|
+
phase: 'LEARN',
|
|
72
|
+
event: 'phase_started',
|
|
73
|
+
data: {
|
|
74
|
+
durationMs: summary.metrics.learnMs
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
timeline.push({
|
|
78
|
+
timestamp: startedAt ? new Date(Date.parse(startedAt) + summary.metrics.learnMs).toISOString() : null,
|
|
79
|
+
phase: 'LEARN',
|
|
80
|
+
event: 'phase_completed',
|
|
81
|
+
data: {
|
|
82
|
+
durationMs: summary.metrics.learnMs,
|
|
83
|
+
expectationsFound: summary.digest?.expectationsTotal || 0
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// OBSERVE phase
|
|
89
|
+
if (summary?.metrics?.observeMs) {
|
|
90
|
+
const observeStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0)).toISOString() : null;
|
|
91
|
+
timeline.push({
|
|
92
|
+
timestamp: observeStart,
|
|
93
|
+
phase: 'OBSERVE',
|
|
94
|
+
event: 'phase_started',
|
|
95
|
+
data: {
|
|
96
|
+
durationMs: summary.metrics.observeMs
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
timeline.push({
|
|
100
|
+
timestamp: observeStart ? new Date(Date.parse(observeStart) + summary.metrics.observeMs).toISOString() : null,
|
|
101
|
+
phase: 'OBSERVE',
|
|
102
|
+
event: 'phase_completed',
|
|
103
|
+
data: {
|
|
104
|
+
durationMs: summary.metrics.observeMs,
|
|
105
|
+
interactionsExecuted: summary.digest?.attempted || 0,
|
|
106
|
+
pagesVisited: summary.digest?.pagesVisited || 0
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// DETECT phase
|
|
112
|
+
if (summary?.metrics?.detectMs) {
|
|
113
|
+
const detectStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0) + (summary.metrics.observeMs || 0)).toISOString() : null;
|
|
114
|
+
timeline.push({
|
|
115
|
+
timestamp: detectStart,
|
|
116
|
+
phase: 'DETECT',
|
|
117
|
+
event: 'phase_started',
|
|
118
|
+
data: {
|
|
119
|
+
durationMs: summary.metrics.detectMs
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
timeline.push({
|
|
123
|
+
timestamp: detectStart ? new Date(Date.parse(detectStart) + summary.metrics.detectMs).toISOString() : null,
|
|
124
|
+
phase: 'DETECT',
|
|
125
|
+
event: 'phase_completed',
|
|
126
|
+
data: {
|
|
127
|
+
durationMs: summary.metrics.detectMs,
|
|
128
|
+
findingsDetected: Array.isArray(findings?.findings) ? findings.findings.length : 0
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Adaptive events from decisions
|
|
134
|
+
if (decisions?.adaptiveEvents) {
|
|
135
|
+
for (const event of decisions.adaptiveEvents) {
|
|
136
|
+
timeline.push({
|
|
137
|
+
timestamp: event.timestamp || null,
|
|
138
|
+
phase: event.phase || 'UNKNOWN',
|
|
139
|
+
event: 'adaptive_decision',
|
|
140
|
+
data: {
|
|
141
|
+
decisionId: event.decisionId,
|
|
142
|
+
reason: event.reason,
|
|
143
|
+
context: event.context || {}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Budget violations from performance report
|
|
150
|
+
if (performanceReport?.violations) {
|
|
151
|
+
for (const violation of performanceReport.violations) {
|
|
152
|
+
timeline.push({
|
|
153
|
+
timestamp: performanceReport.generatedAt || null,
|
|
154
|
+
phase: 'PERFORMANCE',
|
|
155
|
+
event: 'budget_violation',
|
|
156
|
+
severity: 'BLOCKING',
|
|
157
|
+
data: {
|
|
158
|
+
type: violation.type,
|
|
159
|
+
actual: violation.actual,
|
|
160
|
+
budget: violation.budget,
|
|
161
|
+
message: violation.message
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (performanceReport?.warnings) {
|
|
168
|
+
for (const warning of performanceReport.warnings) {
|
|
169
|
+
timeline.push({
|
|
170
|
+
timestamp: performanceReport.generatedAt || null,
|
|
171
|
+
phase: 'PERFORMANCE',
|
|
172
|
+
event: 'budget_warning',
|
|
173
|
+
severity: 'DEGRADED',
|
|
174
|
+
data: {
|
|
175
|
+
type: warning.type,
|
|
176
|
+
actual: warning.actual,
|
|
177
|
+
budget: warning.budget,
|
|
178
|
+
message: warning.message
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Guardrails applied (from findings)
|
|
185
|
+
if (findings?.findings) {
|
|
186
|
+
for (const finding of findings.findings) {
|
|
187
|
+
if (finding.guardrails?.appliedRules && finding.guardrails.appliedRules.length > 0) {
|
|
188
|
+
timeline.push({
|
|
189
|
+
timestamp: finding.timestamp || null,
|
|
190
|
+
phase: 'DETECT',
|
|
191
|
+
event: 'guardrails_applied',
|
|
192
|
+
data: {
|
|
193
|
+
findingId: finding.findingId || finding.id,
|
|
194
|
+
rules: finding.guardrails.appliedRules.map(r => r.id || r),
|
|
195
|
+
finalDecision: finding.guardrails.finalDecision
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Evidence enforcement (from findings)
|
|
203
|
+
if (findings?.findings) {
|
|
204
|
+
for (const finding of findings.findings) {
|
|
205
|
+
if (finding.evidencePackage) {
|
|
206
|
+
timeline.push({
|
|
207
|
+
timestamp: finding.timestamp || null,
|
|
208
|
+
phase: 'DETECT',
|
|
209
|
+
event: 'evidence_enforced',
|
|
210
|
+
data: {
|
|
211
|
+
findingId: finding.findingId || finding.id,
|
|
212
|
+
isComplete: finding.evidencePackage.isComplete,
|
|
213
|
+
status: finding.severity || finding.status
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Failures from failure ledger
|
|
221
|
+
if (failureLedger?.failures) {
|
|
222
|
+
for (const failure of failureLedger.failures) {
|
|
223
|
+
timeline.push({
|
|
224
|
+
timestamp: failure.timestamp || (startedAt ? new Date(Date.parse(startedAt) + (failure.relativeTime || 0)).toISOString() : null),
|
|
225
|
+
phase: failure.phase || 'UNKNOWN',
|
|
226
|
+
event: 'failure_recorded',
|
|
227
|
+
severity: failure.severity || 'WARNING',
|
|
228
|
+
data: {
|
|
229
|
+
code: failure.code,
|
|
230
|
+
message: failure.message,
|
|
231
|
+
category: failure.category,
|
|
232
|
+
recoverable: failure.recoverable
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Run completion
|
|
239
|
+
if (completedAt) {
|
|
240
|
+
timeline.push({
|
|
241
|
+
timestamp: completedAt,
|
|
242
|
+
phase: 'RUN_COMPLETE',
|
|
243
|
+
event: 'run_completed',
|
|
244
|
+
data: {
|
|
245
|
+
status: summary?.status || runStatus?.status || 'UNKNOWN',
|
|
246
|
+
totalDurationMs: startedAt && completedAt ? Date.parse(completedAt) - Date.parse(startedAt) : null
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Sort by timestamp
|
|
252
|
+
timeline.sort((a, b) => {
|
|
253
|
+
if (!a.timestamp && !b.timestamp) return 0;
|
|
254
|
+
if (!a.timestamp) return 1;
|
|
255
|
+
if (!b.timestamp) return -1;
|
|
256
|
+
return Date.parse(a.timestamp) - Date.parse(b.timestamp);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
runId,
|
|
261
|
+
startedAt,
|
|
262
|
+
completedAt,
|
|
263
|
+
events: timeline,
|
|
264
|
+
summary: {
|
|
265
|
+
totalEvents: timeline.length,
|
|
266
|
+
byPhase: timeline.reduce((acc, e) => {
|
|
267
|
+
acc[e.phase] = (acc[e.phase] || 0) + 1;
|
|
268
|
+
return acc;
|
|
269
|
+
}, {}),
|
|
270
|
+
byEvent: timeline.reduce((acc, e) => {
|
|
271
|
+
acc[e.event] = (acc[e.event] || 0) + 1;
|
|
272
|
+
return acc;
|
|
273
|
+
}, {}),
|
|
274
|
+
blockingViolations: timeline.filter(e => e.severity === 'BLOCKING').length,
|
|
275
|
+
degradedWarnings: timeline.filter(e => e.severity === 'DEGRADED').length
|
|
276
|
+
},
|
|
277
|
+
generatedAt: new Date().toISOString()
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Write timeline to file
|
|
283
|
+
*
|
|
284
|
+
* @param {string} projectDir - Project directory
|
|
285
|
+
* @param {string} runId - Run ID
|
|
286
|
+
* @param {Object} timeline - Timeline object
|
|
287
|
+
* @returns {string} Path to written file
|
|
288
|
+
*/
|
|
289
|
+
export function writeRunTimeline(projectDir, runId, timeline) {
|
|
290
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
291
|
+
const outputPath = resolve(runDir, 'run.timeline.json');
|
|
292
|
+
writeFileSync(outputPath, JSON.stringify(timeline, null, 2), 'utf-8');
|
|
293
|
+
return outputPath;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Load timeline from file
|
|
298
|
+
*
|
|
299
|
+
* @param {string} projectDir - Project directory
|
|
300
|
+
* @param {string} runId - Run ID
|
|
301
|
+
* @returns {Object|null} Timeline or null
|
|
302
|
+
*/
|
|
303
|
+
export function loadRunTimeline(projectDir, runId) {
|
|
304
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
305
|
+
const timelinePath = resolve(runDir, 'run.timeline.json');
|
|
306
|
+
|
|
307
|
+
if (!existsSync(timelinePath)) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
313
|
+
return JSON.parse(readFileSync(timelinePath, 'utf-8'));
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Budget Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines hard limits for runtime, memory, pages, interactions, and artifacts.
|
|
5
|
+
* Exceeding runtime or memory = BLOCKING.
|
|
6
|
+
* Exceeding interactions/pages = DEGRADED.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default performance budgets
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_PERF_BUDGET = {
|
|
13
|
+
// Runtime limits (ms)
|
|
14
|
+
maxRuntimeMs: 300000, // 5 minutes
|
|
15
|
+
|
|
16
|
+
// Memory limits (bytes)
|
|
17
|
+
maxMemoryRSS: 1024 * 1024 * 1024, // 1GB
|
|
18
|
+
|
|
19
|
+
// Page/interaction limits
|
|
20
|
+
maxPagesVisited: 100,
|
|
21
|
+
maxInteractionsExecuted: 500,
|
|
22
|
+
|
|
23
|
+
// Artifact size limits (bytes)
|
|
24
|
+
maxArtifactsSizeMB: 500, // 500MB
|
|
25
|
+
|
|
26
|
+
// Event loop delay threshold (ms)
|
|
27
|
+
maxEventLoopDelayMs: 100
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Performance budget profiles
|
|
32
|
+
*/
|
|
33
|
+
export const PERF_BUDGET_PROFILES = {
|
|
34
|
+
QUICK: {
|
|
35
|
+
maxRuntimeMs: 20000, // 20s
|
|
36
|
+
maxMemoryRSS: 512 * 1024 * 1024, // 512MB
|
|
37
|
+
maxPagesVisited: 5,
|
|
38
|
+
maxInteractionsExecuted: 50,
|
|
39
|
+
maxArtifactsSizeMB: 50,
|
|
40
|
+
maxEventLoopDelayMs: 50
|
|
41
|
+
},
|
|
42
|
+
STANDARD: {
|
|
43
|
+
...DEFAULT_PERF_BUDGET
|
|
44
|
+
},
|
|
45
|
+
THOROUGH: {
|
|
46
|
+
maxRuntimeMs: 600000, // 10 minutes
|
|
47
|
+
maxMemoryRSS: 2 * 1024 * 1024 * 1024, // 2GB
|
|
48
|
+
maxPagesVisited: 200,
|
|
49
|
+
maxInteractionsExecuted: 1000,
|
|
50
|
+
maxArtifactsSizeMB: 1000, // 1GB
|
|
51
|
+
maxEventLoopDelayMs: 200
|
|
52
|
+
},
|
|
53
|
+
EXHAUSTIVE: {
|
|
54
|
+
maxRuntimeMs: 1800000, // 30 minutes
|
|
55
|
+
maxMemoryRSS: 4 * 1024 * 1024 * 1024, // 4GB
|
|
56
|
+
maxPagesVisited: 500,
|
|
57
|
+
maxInteractionsExecuted: 5000,
|
|
58
|
+
maxArtifactsSizeMB: 2000, // 2GB
|
|
59
|
+
maxEventLoopDelayMs: 500
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get performance budget for a profile
|
|
65
|
+
*
|
|
66
|
+
* @param {string} profileName - Profile name (QUICK, STANDARD, THOROUGH, EXHAUSTIVE)
|
|
67
|
+
* @returns {Object} Performance budget
|
|
68
|
+
*/
|
|
69
|
+
export function getPerfBudget(profileName = 'STANDARD') {
|
|
70
|
+
const profile = PERF_BUDGET_PROFILES[profileName.toUpperCase()];
|
|
71
|
+
return profile || PERF_BUDGET_PROFILES.STANDARD;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Evaluate performance against budget
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} actual - Actual performance metrics
|
|
78
|
+
* @param {Object} budget - Performance budget
|
|
79
|
+
* @returns {Object} Evaluation result
|
|
80
|
+
*/
|
|
81
|
+
export function evaluatePerfBudget(actual, budget) {
|
|
82
|
+
const violations = [];
|
|
83
|
+
const warnings = [];
|
|
84
|
+
|
|
85
|
+
// BLOCKING violations
|
|
86
|
+
if (actual.runtimeMs > budget.maxRuntimeMs) {
|
|
87
|
+
violations.push({
|
|
88
|
+
type: 'RUNTIME_EXCEEDED',
|
|
89
|
+
severity: 'BLOCKING',
|
|
90
|
+
actual: actual.runtimeMs,
|
|
91
|
+
budget: budget.maxRuntimeMs,
|
|
92
|
+
excess: actual.runtimeMs - budget.maxRuntimeMs,
|
|
93
|
+
message: `Runtime exceeded: ${actual.runtimeMs}ms > ${budget.maxRuntimeMs}ms`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (actual.memoryRSS > budget.maxMemoryRSS) {
|
|
98
|
+
violations.push({
|
|
99
|
+
type: 'MEMORY_EXCEEDED',
|
|
100
|
+
severity: 'BLOCKING',
|
|
101
|
+
actual: actual.memoryRSS,
|
|
102
|
+
budget: budget.maxMemoryRSS,
|
|
103
|
+
excess: actual.memoryRSS - budget.maxMemoryRSS,
|
|
104
|
+
message: `Memory exceeded: ${formatBytes(actual.memoryRSS)} > ${formatBytes(budget.maxMemoryRSS)}`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DEGRADED violations
|
|
109
|
+
if (actual.pagesVisited > budget.maxPagesVisited) {
|
|
110
|
+
warnings.push({
|
|
111
|
+
type: 'PAGES_EXCEEDED',
|
|
112
|
+
severity: 'DEGRADED',
|
|
113
|
+
actual: actual.pagesVisited,
|
|
114
|
+
budget: budget.maxPagesVisited,
|
|
115
|
+
excess: actual.pagesVisited - budget.maxPagesVisited,
|
|
116
|
+
message: `Pages visited exceeded: ${actual.pagesVisited} > ${budget.maxPagesVisited}`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (actual.interactionsExecuted > budget.maxInteractionsExecuted) {
|
|
121
|
+
warnings.push({
|
|
122
|
+
type: 'INTERACTIONS_EXCEEDED',
|
|
123
|
+
severity: 'DEGRADED',
|
|
124
|
+
actual: actual.interactionsExecuted,
|
|
125
|
+
budget: budget.maxInteractionsExecuted,
|
|
126
|
+
excess: actual.interactionsExecuted - budget.maxInteractionsExecuted,
|
|
127
|
+
message: `Interactions executed exceeded: ${actual.interactionsExecuted} > ${budget.maxInteractionsExecuted}`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (actual.artifactsSizeMB > budget.maxArtifactsSizeMB) {
|
|
132
|
+
warnings.push({
|
|
133
|
+
type: 'ARTIFACTS_SIZE_EXCEEDED',
|
|
134
|
+
severity: 'DEGRADED',
|
|
135
|
+
actual: actual.artifactsSizeMB,
|
|
136
|
+
budget: budget.maxArtifactsSizeMB,
|
|
137
|
+
excess: actual.artifactsSizeMB - budget.maxArtifactsSizeMB,
|
|
138
|
+
message: `Artifacts size exceeded: ${actual.artifactsSizeMB}MB > ${budget.maxArtifactsSizeMB}MB`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (actual.eventLoopDelayMs > budget.maxEventLoopDelayMs) {
|
|
143
|
+
warnings.push({
|
|
144
|
+
type: 'EVENT_LOOP_DELAY_EXCEEDED',
|
|
145
|
+
severity: 'DEGRADED',
|
|
146
|
+
actual: actual.eventLoopDelayMs,
|
|
147
|
+
budget: budget.maxEventLoopDelayMs,
|
|
148
|
+
excess: actual.eventLoopDelayMs - budget.maxEventLoopDelayMs,
|
|
149
|
+
message: `Event loop delay exceeded: ${actual.eventLoopDelayMs}ms > ${budget.maxEventLoopDelayMs}ms`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hasBlocking = violations.length > 0;
|
|
154
|
+
const hasDegraded = warnings.length > 0;
|
|
155
|
+
|
|
156
|
+
let verdict = 'OK';
|
|
157
|
+
if (hasBlocking) {
|
|
158
|
+
verdict = 'BLOCKED';
|
|
159
|
+
} else if (hasDegraded) {
|
|
160
|
+
verdict = 'DEGRADED';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
verdict,
|
|
165
|
+
ok: !hasBlocking,
|
|
166
|
+
violations,
|
|
167
|
+
warnings,
|
|
168
|
+
summary: {
|
|
169
|
+
blocking: violations.length,
|
|
170
|
+
degraded: warnings.length,
|
|
171
|
+
total: violations.length + warnings.length
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Format bytes to human-readable string
|
|
178
|
+
*/
|
|
179
|
+
function formatBytes(bytes) {
|
|
180
|
+
if (bytes === 0) return '0 B';
|
|
181
|
+
const k = 1024;
|
|
182
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
183
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
184
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
185
|
+
}
|
|
186
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Display
|
|
3
|
+
*
|
|
4
|
+
* Formats performance metrics for CLI display.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadPerformanceReport } from './perf.report.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format performance metrics for display
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} report - Performance report
|
|
13
|
+
* @returns {string} Formatted string
|
|
14
|
+
*/
|
|
15
|
+
export function formatPerformanceMetrics(report) {
|
|
16
|
+
if (!report) {
|
|
17
|
+
return 'Performance: No report available';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push('Performance:');
|
|
22
|
+
|
|
23
|
+
// Runtime
|
|
24
|
+
const runtimeSec = (report.actual.runtimeMs / 1000).toFixed(1);
|
|
25
|
+
const runtimeBudgetSec = (report.budget.maxRuntimeMs / 1000).toFixed(0);
|
|
26
|
+
const runtimeStatus = report.actual.runtimeMs <= report.budget.maxRuntimeMs ? 'OK' : 'EXCEEDED';
|
|
27
|
+
lines.push(` Runtime: ${runtimeSec}s / ${runtimeBudgetSec}s ${runtimeStatus}`);
|
|
28
|
+
|
|
29
|
+
// Memory
|
|
30
|
+
const memoryMB = (report.actual.memoryRSS / (1024 * 1024)).toFixed(0);
|
|
31
|
+
const memoryBudgetMB = (report.budget.maxMemoryRSS / (1024 * 1024)).toFixed(0);
|
|
32
|
+
const memoryStatus = report.actual.memoryRSS <= report.budget.maxMemoryRSS ? 'OK' : 'EXCEEDED';
|
|
33
|
+
lines.push(` Memory: ${memoryMB}MB / ${memoryBudgetMB}MB ${memoryStatus}`);
|
|
34
|
+
|
|
35
|
+
// Pages
|
|
36
|
+
const pagesStatus = report.actual.pagesVisited <= report.budget.maxPagesVisited ? 'OK' : 'EXCEEDED';
|
|
37
|
+
lines.push(` Pages: ${report.actual.pagesVisited} / ${report.budget.maxPagesVisited} ${pagesStatus}`);
|
|
38
|
+
|
|
39
|
+
// Interactions
|
|
40
|
+
const interactionsStatus = report.actual.interactionsExecuted <= report.budget.maxInteractionsExecuted ? 'OK' : 'EXCEEDED';
|
|
41
|
+
lines.push(` Interactions: ${report.actual.interactionsExecuted} / ${report.budget.maxInteractionsExecuted} ${interactionsStatus}`);
|
|
42
|
+
|
|
43
|
+
// Verdict
|
|
44
|
+
const verdictSymbol = report.verdict === 'OK' ? '✅' : report.verdict === 'DEGRADED' ? '⚠️' : '❌';
|
|
45
|
+
lines.push(`Verdict: ${verdictSymbol} ${report.verdict}`);
|
|
46
|
+
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Display performance metrics in inspect command
|
|
52
|
+
*
|
|
53
|
+
* @param {string} projectDir - Project directory
|
|
54
|
+
* @param {string} runId - Run ID
|
|
55
|
+
*/
|
|
56
|
+
export function displayPerformanceInInspect(projectDir, runId) {
|
|
57
|
+
const report = loadPerformanceReport(projectDir, runId);
|
|
58
|
+
|
|
59
|
+
if (!report) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('\n' + formatPerformanceMetrics(report));
|
|
64
|
+
}
|
|
65
|
+
|