@evalgate/sdk 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 +638 -0
- package/README.md +398 -0
- package/dist/assertions.d.ts +189 -0
- package/dist/assertions.js +662 -0
- package/dist/batch.d.ts +68 -0
- package/dist/batch.js +179 -0
- package/dist/cache.d.ts +65 -0
- package/dist/cache.js +131 -0
- package/dist/cli/api.d.ts +108 -0
- package/dist/cli/api.js +132 -0
- package/dist/cli/baseline.d.ts +10 -0
- package/dist/cli/baseline.js +172 -0
- package/dist/cli/check.d.ts +73 -0
- package/dist/cli/check.js +355 -0
- package/dist/cli/ci-context.d.ts +6 -0
- package/dist/cli/ci-context.js +112 -0
- package/dist/cli/ci.d.ts +45 -0
- package/dist/cli/ci.js +192 -0
- package/dist/cli/config.d.ts +30 -0
- package/dist/cli/config.js +230 -0
- package/dist/cli/constants.d.ts +15 -0
- package/dist/cli/constants.js +18 -0
- package/dist/cli/diff.d.ts +173 -0
- package/dist/cli/diff.js +685 -0
- package/dist/cli/discover.d.ts +84 -0
- package/dist/cli/discover.js +419 -0
- package/dist/cli/doctor.d.ts +88 -0
- package/dist/cli/doctor.js +675 -0
- package/dist/cli/env.d.ts +21 -0
- package/dist/cli/env.js +42 -0
- package/dist/cli/explain.d.ts +58 -0
- package/dist/cli/explain.js +561 -0
- package/dist/cli/formatters/github.d.ts +8 -0
- package/dist/cli/formatters/github.js +135 -0
- package/dist/cli/formatters/human.d.ts +6 -0
- package/dist/cli/formatters/human.js +110 -0
- package/dist/cli/formatters/json.d.ts +6 -0
- package/dist/cli/formatters/json.js +10 -0
- package/dist/cli/formatters/pr-comment.d.ts +12 -0
- package/dist/cli/formatters/pr-comment.js +103 -0
- package/dist/cli/formatters/types.d.ts +103 -0
- package/dist/cli/formatters/types.js +8 -0
- package/dist/cli/gate.d.ts +21 -0
- package/dist/cli/gate.js +179 -0
- package/dist/cli/impact-analysis.d.ts +63 -0
- package/dist/cli/impact-analysis.js +252 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +332 -0
- package/dist/cli/init.d.ts +16 -0
- package/dist/cli/init.js +292 -0
- package/dist/cli/manifest.d.ts +103 -0
- package/dist/cli/manifest.js +282 -0
- package/dist/cli/migrate.d.ts +41 -0
- package/dist/cli/migrate.js +349 -0
- package/dist/cli/policy-packs.d.ts +23 -0
- package/dist/cli/policy-packs.js +89 -0
- package/dist/cli/print-config.d.ts +29 -0
- package/dist/cli/print-config.js +270 -0
- package/dist/cli/profiles.d.ts +28 -0
- package/dist/cli/profiles.js +30 -0
- package/dist/cli/reason-codes.d.ts +17 -0
- package/dist/cli/reason-codes.js +19 -0
- package/dist/cli/regression-gate.d.ts +15 -0
- package/dist/cli/regression-gate.js +341 -0
- package/dist/cli/render/snippet.d.ts +5 -0
- package/dist/cli/render/snippet.js +15 -0
- package/dist/cli/render/sort.d.ts +10 -0
- package/dist/cli/render/sort.js +24 -0
- package/dist/cli/report/build-check-report.d.ts +19 -0
- package/dist/cli/report/build-check-report.js +132 -0
- package/dist/cli/run.d.ts +101 -0
- package/dist/cli/run.js +395 -0
- package/dist/cli/share.d.ts +17 -0
- package/dist/cli/share.js +91 -0
- package/dist/cli/upgrade.d.ts +15 -0
- package/dist/cli/upgrade.js +492 -0
- package/dist/cli/workspace.d.ts +31 -0
- package/dist/cli/workspace.js +68 -0
- package/dist/client.d.ts +368 -0
- package/dist/client.js +893 -0
- package/dist/client.request.test.d.ts +1 -0
- package/dist/client.request.test.js +232 -0
- package/dist/context.d.ts +134 -0
- package/dist/context.js +215 -0
- package/dist/errors.d.ts +82 -0
- package/dist/errors.js +298 -0
- package/dist/export.d.ts +195 -0
- package/dist/export.js +344 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +153 -0
- package/dist/integrations/anthropic.d.ts +91 -0
- package/dist/integrations/anthropic.js +163 -0
- package/dist/integrations/openai-eval.d.ts +57 -0
- package/dist/integrations/openai-eval.js +232 -0
- package/dist/integrations/openai.d.ts +92 -0
- package/dist/integrations/openai.js +160 -0
- package/dist/local.d.ts +39 -0
- package/dist/local.js +148 -0
- package/dist/logger.d.ts +128 -0
- package/dist/logger.js +227 -0
- package/dist/matchers/index.d.ts +1 -0
- package/dist/matchers/index.js +6 -0
- package/dist/matchers/to-pass-gate.d.ts +29 -0
- package/dist/matchers/to-pass-gate.js +35 -0
- package/dist/pagination.d.ts +74 -0
- package/dist/pagination.js +139 -0
- package/dist/regression.d.ts +100 -0
- package/dist/regression.js +44 -0
- package/dist/runtime/adapters/config-to-dsl.d.ts +33 -0
- package/dist/runtime/adapters/config-to-dsl.js +400 -0
- package/dist/runtime/adapters/testsuite-to-dsl.d.ts +63 -0
- package/dist/runtime/adapters/testsuite-to-dsl.js +276 -0
- package/dist/runtime/context.d.ts +26 -0
- package/dist/runtime/context.js +74 -0
- package/dist/runtime/eval.d.ts +46 -0
- package/dist/runtime/eval.js +244 -0
- package/dist/runtime/execution-mode.d.ts +80 -0
- package/dist/runtime/execution-mode.js +357 -0
- package/dist/runtime/executor.d.ts +16 -0
- package/dist/runtime/executor.js +152 -0
- package/dist/runtime/registry.d.ts +78 -0
- package/dist/runtime/registry.js +403 -0
- package/dist/runtime/run-report.d.ts +200 -0
- package/dist/runtime/run-report.js +222 -0
- package/dist/runtime/types.d.ts +356 -0
- package/dist/runtime/types.js +76 -0
- package/dist/snapshot.d.ts +176 -0
- package/dist/snapshot.js +322 -0
- package/dist/streaming.d.ts +173 -0
- package/dist/streaming.js +268 -0
- package/dist/testing.d.ts +273 -0
- package/dist/testing.js +317 -0
- package/dist/types.d.ts +754 -0
- package/dist/types.js +54 -0
- package/dist/utils/input-hash.d.ts +8 -0
- package/dist/utils/input-hash.js +41 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.js +10 -0
- package/dist/workflows.d.ts +389 -0
- package/dist/workflows.js +671 -0
- package/package.json +117 -0
package/dist/cli/gate.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure gate evaluation. No console output.
|
|
4
|
+
* Baseline missing ā configuration failure (BAD_ARGS), not API_ERROR.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.evaluateGate = evaluateGate;
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
const policy_packs_1 = require("./policy-packs");
|
|
10
|
+
const reason_codes_1 = require("./reason-codes");
|
|
11
|
+
function evaluateGate(args, quality) {
|
|
12
|
+
const score = quality?.score ?? 0;
|
|
13
|
+
const total = quality?.total ?? null;
|
|
14
|
+
const evidenceLevel = quality?.evidenceLevel ?? null;
|
|
15
|
+
const _baselineScore = quality?.baselineScore ?? null;
|
|
16
|
+
const regressionDelta = quality?.regressionDelta ?? null;
|
|
17
|
+
const baselineMissing = quality?.baselineMissing === true;
|
|
18
|
+
const breakdown = quality?.breakdown ?? {};
|
|
19
|
+
const policyFlags = (quality?.flags ?? []);
|
|
20
|
+
const avgLatencyMs = quality?.avgLatencyMs ?? null;
|
|
21
|
+
const costUsd = quality?.costUsd ?? null;
|
|
22
|
+
const baselineCostUsd = quality?.baselineCostUsd ?? null;
|
|
23
|
+
// Baseline missing FIRST: --baseline auto ā exit 0 (neutral, gate not applied); others ā BAD_ARGS
|
|
24
|
+
// Must run before budget gates so baseline missing + maxCostDeltaUsd ā neutral, not budget failure
|
|
25
|
+
if (baselineMissing) {
|
|
26
|
+
const msg = args.baseline === "auto"
|
|
27
|
+
? "No baseline found. Tip: Publish a baseline from the dashboard, or run with --baseline previous once you have runs."
|
|
28
|
+
: args.baseline === "production"
|
|
29
|
+
? "No prod runs exist for this evaluation. Tag runs with environment=prod before using --baseline production."
|
|
30
|
+
: `Baseline (${args.baseline}) not found. Ensure a baseline run exists (e.g. published run, previous run, or prod-tagged run).`;
|
|
31
|
+
if (args.baseline === "auto") {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: constants_1.EXIT.PASS,
|
|
34
|
+
passed: false,
|
|
35
|
+
reasonCode: reason_codes_1.REASON_CODES.BASELINE_MISSING,
|
|
36
|
+
reasonMessage: msg,
|
|
37
|
+
gateSkipped: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (args.baseline !== "published" || args.maxDrop !== undefined) {
|
|
41
|
+
return {
|
|
42
|
+
exitCode: constants_1.EXIT.BAD_ARGS,
|
|
43
|
+
passed: false,
|
|
44
|
+
reasonCode: reason_codes_1.REASON_CODES.BASELINE_MISSING,
|
|
45
|
+
reasonMessage: msg,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Budget gates (after baseline check)
|
|
50
|
+
if (args.maxCostUsd != null && costUsd != null && costUsd > args.maxCostUsd) {
|
|
51
|
+
return {
|
|
52
|
+
exitCode: constants_1.EXIT.SCORE_BELOW,
|
|
53
|
+
passed: false,
|
|
54
|
+
reasonCode: reason_codes_1.REASON_CODES.COST_BUDGET_EXCEEDED,
|
|
55
|
+
reasonMessage: `cost $${costUsd.toFixed(4)} exceeds maxCostUsd $${args.maxCostUsd.toFixed(4)}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (args.maxLatencyMs != null &&
|
|
59
|
+
avgLatencyMs != null &&
|
|
60
|
+
avgLatencyMs > args.maxLatencyMs) {
|
|
61
|
+
return {
|
|
62
|
+
exitCode: constants_1.EXIT.SCORE_BELOW,
|
|
63
|
+
passed: false,
|
|
64
|
+
reasonCode: reason_codes_1.REASON_CODES.LATENCY_BUDGET_EXCEEDED,
|
|
65
|
+
reasonMessage: `avg latency ${avgLatencyMs}ms exceeds maxLatencyMs ${args.maxLatencyMs}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (args.maxCostDeltaUsd != null &&
|
|
69
|
+
costUsd != null &&
|
|
70
|
+
baselineCostUsd != null &&
|
|
71
|
+
costUsd - baselineCostUsd > args.maxCostDeltaUsd) {
|
|
72
|
+
return {
|
|
73
|
+
exitCode: constants_1.EXIT.SCORE_BELOW,
|
|
74
|
+
passed: false,
|
|
75
|
+
reasonCode: reason_codes_1.REASON_CODES.COST_BUDGET_EXCEEDED,
|
|
76
|
+
reasonMessage: `cost delta $${(costUsd - baselineCostUsd).toFixed(4)} exceeds maxCostDeltaUsd $${args.maxCostDeltaUsd.toFixed(4)}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// minN gate
|
|
80
|
+
if (args.minN !== undefined && total !== null && total < args.minN) {
|
|
81
|
+
return {
|
|
82
|
+
exitCode: constants_1.EXIT.LOW_N,
|
|
83
|
+
passed: false,
|
|
84
|
+
reasonCode: reason_codes_1.REASON_CODES.LOW_SAMPLE_SIZE,
|
|
85
|
+
reasonMessage: `total test cases (${total}) < minN (${args.minN})`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// allowWeakEvidence gate
|
|
89
|
+
if (!args.allowWeakEvidence && evidenceLevel === "weak") {
|
|
90
|
+
return {
|
|
91
|
+
exitCode: constants_1.EXIT.WEAK_EVIDENCE,
|
|
92
|
+
passed: false,
|
|
93
|
+
reasonCode: reason_codes_1.REASON_CODES.LOW_SAMPLE_SIZE,
|
|
94
|
+
reasonMessage: "evidence level is 'weak' (use --allowWeakEvidence to permit)",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Compute gate result
|
|
98
|
+
if (args.minScore > 0 && score < args.minScore) {
|
|
99
|
+
return {
|
|
100
|
+
exitCode: constants_1.EXIT.SCORE_BELOW,
|
|
101
|
+
passed: false,
|
|
102
|
+
reasonCode: reason_codes_1.REASON_CODES.SCORE_TOO_LOW,
|
|
103
|
+
reasonMessage: `score ${score} < minScore ${args.minScore}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// warnDrop: soft warning band; maxDrop: hard fail
|
|
107
|
+
if (args.maxDrop !== undefined &&
|
|
108
|
+
regressionDelta !== null &&
|
|
109
|
+
regressionDelta < -args.maxDrop) {
|
|
110
|
+
return {
|
|
111
|
+
exitCode: constants_1.EXIT.REGRESSION,
|
|
112
|
+
passed: false,
|
|
113
|
+
reasonCode: reason_codes_1.REASON_CODES.DELTA_TOO_HIGH,
|
|
114
|
+
reasonMessage: `score dropped ${Math.abs(regressionDelta)} pts from baseline (max allowed: ${args.maxDrop})`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (args.warnDrop !== undefined &&
|
|
118
|
+
regressionDelta !== null &&
|
|
119
|
+
regressionDelta < -args.warnDrop &&
|
|
120
|
+
(args.maxDrop === undefined || regressionDelta >= -args.maxDrop)) {
|
|
121
|
+
return {
|
|
122
|
+
exitCode: constants_1.EXIT.WARN_REGRESSION,
|
|
123
|
+
passed: true, // gate passes but with warning
|
|
124
|
+
reasonCode: reason_codes_1.REASON_CODES.WARN_REGRESSION,
|
|
125
|
+
reasonMessage: `score dropped ${Math.abs(regressionDelta)} pts from baseline (warn threshold: ${args.warnDrop}${args.maxDrop != null ? `, fail at ${args.maxDrop}` : ""})`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (args.policy) {
|
|
129
|
+
const pack = (0, policy_packs_1.resolvePolicyPack)(args.policy);
|
|
130
|
+
if (!pack) {
|
|
131
|
+
const valid = (0, policy_packs_1.getValidPolicyVersions)().join(", ");
|
|
132
|
+
return {
|
|
133
|
+
exitCode: constants_1.EXIT.BAD_ARGS,
|
|
134
|
+
passed: false,
|
|
135
|
+
reasonCode: reason_codes_1.REASON_CODES.UNKNOWN,
|
|
136
|
+
reasonMessage: `Unknown policy or version: ${args.policy}. Valid: ${valid}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const { requiredSafetyRate, maxFlags } = pack.thresholds;
|
|
140
|
+
const safetyRate = breakdown?.safety ?? 0;
|
|
141
|
+
if (safetyRate < requiredSafetyRate) {
|
|
142
|
+
return {
|
|
143
|
+
exitCode: constants_1.EXIT.POLICY_VIOLATION,
|
|
144
|
+
passed: false,
|
|
145
|
+
reasonCode: reason_codes_1.REASON_CODES.POLICY_FAILED,
|
|
146
|
+
reasonMessage: `policy ${pack.policyId}@${pack.version}: safety ${Math.round(safetyRate * 100)}% < required ${Math.round(requiredSafetyRate * 100)}%`,
|
|
147
|
+
policyEvidence: {
|
|
148
|
+
failedCheck: "safety_rate",
|
|
149
|
+
remediation: `Increase safety pass rate to at least ${Math.round(requiredSafetyRate * 100)}%. Review failing test cases for safety-related assertions.`,
|
|
150
|
+
snapshot: {
|
|
151
|
+
safety: safetyRate,
|
|
152
|
+
required: requiredSafetyRate,
|
|
153
|
+
policy: `${pack.policyId}@${pack.version}`,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const violations = policyFlags.filter((f) => maxFlags.includes(f));
|
|
159
|
+
if (violations.length > 0) {
|
|
160
|
+
return {
|
|
161
|
+
exitCode: constants_1.EXIT.POLICY_VIOLATION,
|
|
162
|
+
passed: false,
|
|
163
|
+
reasonCode: reason_codes_1.REASON_CODES.POLICY_FAILED,
|
|
164
|
+
reasonMessage: `policy ${pack.policyId}@${pack.version}: ${violations.join(", ")}`,
|
|
165
|
+
policyEvidence: {
|
|
166
|
+
failedCheck: "flag_restrictions",
|
|
167
|
+
remediation: `Resolve flags: ${violations.join(", ")}. These indicate policy violations that must be addressed.`,
|
|
168
|
+
snapshot: { violations, policy: `${pack.policyId}@${pack.version}` },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
exitCode: constants_1.EXIT.PASS,
|
|
175
|
+
passed: true,
|
|
176
|
+
reasonCode: reason_codes_1.REASON_CODES.PASS,
|
|
177
|
+
reasonMessage: null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TICKET 3 ā Impact Analysis CLI Command (v0)
|
|
3
|
+
*
|
|
4
|
+
* Goal: Modal-like perceived speed via incremental intelligence
|
|
5
|
+
*
|
|
6
|
+
* Algorithm v0 (practical, shippable):
|
|
7
|
+
* - Inputs: manifest.json + git diff --name-only base...HEAD
|
|
8
|
+
* - Rules: Direct file mapping, dependency tracking, safe fallback
|
|
9
|
+
* - Output: Human-readable counts + JSON for automation
|
|
10
|
+
*/
|
|
11
|
+
import type { EvaluationManifest } from "./manifest";
|
|
12
|
+
/**
|
|
13
|
+
* Impact analysis result
|
|
14
|
+
*/
|
|
15
|
+
export interface ImpactAnalysisResult {
|
|
16
|
+
/** Impacted specification IDs */
|
|
17
|
+
impactedSpecIds: string[];
|
|
18
|
+
/** Reason for each impacted spec */
|
|
19
|
+
reasonBySpecId: Record<string, string>;
|
|
20
|
+
/** Changed files that triggered the analysis */
|
|
21
|
+
changedFiles: string[];
|
|
22
|
+
/** Analysis metadata */
|
|
23
|
+
metadata: {
|
|
24
|
+
baseBranch: string;
|
|
25
|
+
totalSpecs: number;
|
|
26
|
+
impactedCount: number;
|
|
27
|
+
analysisTime: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Impact analysis options
|
|
32
|
+
*/
|
|
33
|
+
export interface ImpactAnalysisOptions {
|
|
34
|
+
/** Base branch to compare against */
|
|
35
|
+
baseBranch: string;
|
|
36
|
+
/** Optional explicit list of changed files (for CI) */
|
|
37
|
+
changedFiles?: string[];
|
|
38
|
+
/** Output format */
|
|
39
|
+
format?: "human" | "json";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run impact analysis
|
|
43
|
+
*/
|
|
44
|
+
export declare function runImpactAnalysis(options: ImpactAnalysisOptions, projectRoot?: string): Promise<ImpactAnalysisResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Analyze impact of changed files
|
|
47
|
+
*/
|
|
48
|
+
export declare function analyzeImpact(changedFiles: string[], manifest: EvaluationManifest): {
|
|
49
|
+
impactedSpecIds: string[];
|
|
50
|
+
reasonBySpecId: Record<string, string>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Print human-readable results
|
|
54
|
+
*/
|
|
55
|
+
export declare function printHumanResults(result: ImpactAnalysisResult): void;
|
|
56
|
+
/**
|
|
57
|
+
* Print JSON results
|
|
58
|
+
*/
|
|
59
|
+
export declare function printJsonResults(result: ImpactAnalysisResult): void;
|
|
60
|
+
/**
|
|
61
|
+
* CLI entry point
|
|
62
|
+
*/
|
|
63
|
+
export declare function runImpactAnalysisCLI(options: ImpactAnalysisOptions): Promise<void>;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TICKET 3 ā Impact Analysis CLI Command (v0)
|
|
4
|
+
*
|
|
5
|
+
* Goal: Modal-like perceived speed via incremental intelligence
|
|
6
|
+
*
|
|
7
|
+
* Algorithm v0 (practical, shippable):
|
|
8
|
+
* - Inputs: manifest.json + git diff --name-only base...HEAD
|
|
9
|
+
* - Rules: Direct file mapping, dependency tracking, safe fallback
|
|
10
|
+
* - Output: Human-readable counts + JSON for automation
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.runImpactAnalysis = runImpactAnalysis;
|
|
47
|
+
exports.analyzeImpact = analyzeImpact;
|
|
48
|
+
exports.printHumanResults = printHumanResults;
|
|
49
|
+
exports.printJsonResults = printJsonResults;
|
|
50
|
+
exports.runImpactAnalysisCLI = runImpactAnalysisCLI;
|
|
51
|
+
const node_child_process_1 = require("node:child_process");
|
|
52
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
53
|
+
const path = __importStar(require("node:path"));
|
|
54
|
+
/**
|
|
55
|
+
* Run impact analysis
|
|
56
|
+
*/
|
|
57
|
+
async function runImpactAnalysis(options, projectRoot = process.cwd()) {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
// Read manifest
|
|
60
|
+
const manifest = await readManifest(projectRoot);
|
|
61
|
+
if (!manifest) {
|
|
62
|
+
throw new Error("No evaluation manifest found. Run 'evalgate discover --manifest' first.");
|
|
63
|
+
}
|
|
64
|
+
// Get changed files
|
|
65
|
+
const changedFiles = options.changedFiles || (await getChangedFiles(options.baseBranch));
|
|
66
|
+
// Analyze impact
|
|
67
|
+
const { impactedSpecIds, reasonBySpecId } = analyzeImpact(changedFiles, manifest);
|
|
68
|
+
const result = {
|
|
69
|
+
impactedSpecIds,
|
|
70
|
+
reasonBySpecId,
|
|
71
|
+
changedFiles,
|
|
72
|
+
metadata: {
|
|
73
|
+
baseBranch: options.baseBranch,
|
|
74
|
+
totalSpecs: manifest.specs.length,
|
|
75
|
+
impactedCount: impactedSpecIds.length,
|
|
76
|
+
analysisTime: Date.now() - startTime,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Read evaluation manifest
|
|
83
|
+
*/
|
|
84
|
+
async function readManifest(projectRoot = process.cwd()) {
|
|
85
|
+
const manifestPath = path.join(projectRoot, ".evalgate", "manifest.json");
|
|
86
|
+
try {
|
|
87
|
+
const content = await fs.readFile(manifestPath, "utf-8");
|
|
88
|
+
return JSON.parse(content);
|
|
89
|
+
}
|
|
90
|
+
catch (_error) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get changed files from git
|
|
96
|
+
*/
|
|
97
|
+
async function getChangedFiles(baseBranch) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const git = (0, node_child_process_1.spawn)("git", ["diff", "--name-only", `${baseBranch}...HEAD`], {
|
|
100
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
101
|
+
});
|
|
102
|
+
let output = "";
|
|
103
|
+
let error = "";
|
|
104
|
+
git.stdout?.on("data", (data) => {
|
|
105
|
+
output += data.toString();
|
|
106
|
+
});
|
|
107
|
+
git.stderr?.on("data", (data) => {
|
|
108
|
+
error += data.toString();
|
|
109
|
+
});
|
|
110
|
+
git.on("close", (code) => {
|
|
111
|
+
if (code !== 0) {
|
|
112
|
+
reject(new Error(`Git diff failed: ${error}`));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const files = output
|
|
116
|
+
.split("\n")
|
|
117
|
+
.map((f) => f.trim())
|
|
118
|
+
.filter((f) => f.length > 0)
|
|
119
|
+
.map((f) => f.replace(/\\/g, "/")); // Normalize to POSIX
|
|
120
|
+
resolve(files);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Analyze impact of changed files
|
|
126
|
+
*/
|
|
127
|
+
function analyzeImpact(changedFiles, manifest) {
|
|
128
|
+
const impactedSpecIds = new Set();
|
|
129
|
+
const reasonBySpecId = {};
|
|
130
|
+
// Normalize changed files to POSIX format
|
|
131
|
+
const normalizedChangedFiles = changedFiles.map((f) => f.replace(/\\/g, "/"));
|
|
132
|
+
// Create lookup maps
|
|
133
|
+
const specsByFile = new Map();
|
|
134
|
+
const specsByDependency = new Map();
|
|
135
|
+
// Index specs by file
|
|
136
|
+
for (const spec of manifest.specs) {
|
|
137
|
+
// By file path
|
|
138
|
+
if (!specsByFile.has(spec.filePath)) {
|
|
139
|
+
specsByFile.set(spec.filePath, []);
|
|
140
|
+
}
|
|
141
|
+
specsByFile.get(spec.filePath)?.push(spec);
|
|
142
|
+
// By dependencies
|
|
143
|
+
const deps = [
|
|
144
|
+
...spec.dependsOn.prompts,
|
|
145
|
+
...spec.dependsOn.datasets,
|
|
146
|
+
...spec.dependsOn.tools,
|
|
147
|
+
...spec.dependsOn.code,
|
|
148
|
+
];
|
|
149
|
+
for (const dep of deps) {
|
|
150
|
+
if (!specsByDependency.has(dep)) {
|
|
151
|
+
specsByDependency.set(dep, []);
|
|
152
|
+
}
|
|
153
|
+
specsByDependency.get(dep)?.push(spec);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Analyze each changed file
|
|
157
|
+
for (const changedFile of normalizedChangedFiles) {
|
|
158
|
+
// Rule 1: Direct spec file change
|
|
159
|
+
const specsInFile = specsByFile.get(changedFile);
|
|
160
|
+
if (specsInFile) {
|
|
161
|
+
for (const spec of specsInFile) {
|
|
162
|
+
impactedSpecIds.add(spec.id);
|
|
163
|
+
reasonBySpecId[spec.id] = `Spec file changed: ${changedFile}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Rule 2: Dependency change
|
|
167
|
+
const specsUsingDep = specsByDependency.get(changedFile);
|
|
168
|
+
if (specsUsingDep) {
|
|
169
|
+
for (const spec of specsUsingDep) {
|
|
170
|
+
impactedSpecIds.add(spec.id);
|
|
171
|
+
reasonBySpecId[spec.id] = `Dependency changed: ${changedFile}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Rule 3: Safe fallback for unknown files
|
|
175
|
+
if (!specsInFile && !specsUsingDep) {
|
|
176
|
+
// If we can't map the file, be conservative and run everything
|
|
177
|
+
console.warn(`ā ļø Unknown changed file: ${changedFile}`);
|
|
178
|
+
console.warn(`š”ļø Running full suite for safety`);
|
|
179
|
+
// Add all specs
|
|
180
|
+
for (const spec of manifest.specs) {
|
|
181
|
+
impactedSpecIds.add(spec.id);
|
|
182
|
+
reasonBySpecId[spec.id] =
|
|
183
|
+
`Unknown file changed: ${changedFile} (safe fallback)`;
|
|
184
|
+
}
|
|
185
|
+
break; // No need to continue analyzing
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
impactedSpecIds: Array.from(impactedSpecIds).sort(),
|
|
190
|
+
reasonBySpecId,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Print human-readable results
|
|
195
|
+
*/
|
|
196
|
+
function printHumanResults(result) {
|
|
197
|
+
console.log("\nš Impact Analysis Results");
|
|
198
|
+
console.log(`š Base branch: ${result.metadata.baseBranch}`);
|
|
199
|
+
console.log(`š Changed files: ${result.changedFiles.length}`);
|
|
200
|
+
console.log(`šÆ Impacted specs: ${result.metadata.impactedCount}/${result.metadata.totalSpecs}`);
|
|
201
|
+
console.log(`ā±ļø Analysis time: ${result.metadata.analysisTime}ms`);
|
|
202
|
+
if (result.changedFiles.length > 0) {
|
|
203
|
+
console.log("\nš Changed files:");
|
|
204
|
+
for (const file of result.changedFiles) {
|
|
205
|
+
console.log(` ⢠${file}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (result.impactedSpecIds.length > 0) {
|
|
209
|
+
console.log("\nšÆ Impacted specifications:");
|
|
210
|
+
for (const specId of result.impactedSpecIds) {
|
|
211
|
+
const reason = result.reasonBySpecId[specId];
|
|
212
|
+
console.log(` ⢠${specId} (${reason})`);
|
|
213
|
+
}
|
|
214
|
+
console.log("\nš” Suggested command:");
|
|
215
|
+
console.log(` evalgate run --spec-ids ${result.impactedSpecIds.join(",")}`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log("\nā
No specifications impacted");
|
|
219
|
+
console.log("š” No tests needed to run");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Print JSON results
|
|
224
|
+
*/
|
|
225
|
+
function printJsonResults(result) {
|
|
226
|
+
console.log(JSON.stringify(result, null, 2));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* CLI entry point
|
|
230
|
+
*/
|
|
231
|
+
async function runImpactAnalysisCLI(options) {
|
|
232
|
+
try {
|
|
233
|
+
const result = await runImpactAnalysis(options);
|
|
234
|
+
if (options.format === "json") {
|
|
235
|
+
printJsonResults(result);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
printHumanResults(result);
|
|
239
|
+
}
|
|
240
|
+
// Exit with appropriate code
|
|
241
|
+
if (result.metadata.impactedCount === 0) {
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
process.exit(1); // Signal that tests should run
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
console.error("ā Impact analysis failed:", error instanceof Error ? error.message : String(error));
|
|
250
|
+
process.exit(2);
|
|
251
|
+
}
|
|
252
|
+
}
|