@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/README.md +398 -0
  3. package/dist/assertions.d.ts +189 -0
  4. package/dist/assertions.js +662 -0
  5. package/dist/batch.d.ts +68 -0
  6. package/dist/batch.js +179 -0
  7. package/dist/cache.d.ts +65 -0
  8. package/dist/cache.js +131 -0
  9. package/dist/cli/api.d.ts +108 -0
  10. package/dist/cli/api.js +132 -0
  11. package/dist/cli/baseline.d.ts +10 -0
  12. package/dist/cli/baseline.js +172 -0
  13. package/dist/cli/check.d.ts +73 -0
  14. package/dist/cli/check.js +355 -0
  15. package/dist/cli/ci-context.d.ts +6 -0
  16. package/dist/cli/ci-context.js +112 -0
  17. package/dist/cli/ci.d.ts +45 -0
  18. package/dist/cli/ci.js +192 -0
  19. package/dist/cli/config.d.ts +30 -0
  20. package/dist/cli/config.js +230 -0
  21. package/dist/cli/constants.d.ts +15 -0
  22. package/dist/cli/constants.js +18 -0
  23. package/dist/cli/diff.d.ts +173 -0
  24. package/dist/cli/diff.js +685 -0
  25. package/dist/cli/discover.d.ts +84 -0
  26. package/dist/cli/discover.js +419 -0
  27. package/dist/cli/doctor.d.ts +88 -0
  28. package/dist/cli/doctor.js +675 -0
  29. package/dist/cli/env.d.ts +21 -0
  30. package/dist/cli/env.js +42 -0
  31. package/dist/cli/explain.d.ts +58 -0
  32. package/dist/cli/explain.js +561 -0
  33. package/dist/cli/formatters/github.d.ts +8 -0
  34. package/dist/cli/formatters/github.js +135 -0
  35. package/dist/cli/formatters/human.d.ts +6 -0
  36. package/dist/cli/formatters/human.js +110 -0
  37. package/dist/cli/formatters/json.d.ts +6 -0
  38. package/dist/cli/formatters/json.js +10 -0
  39. package/dist/cli/formatters/pr-comment.d.ts +12 -0
  40. package/dist/cli/formatters/pr-comment.js +103 -0
  41. package/dist/cli/formatters/types.d.ts +103 -0
  42. package/dist/cli/formatters/types.js +8 -0
  43. package/dist/cli/gate.d.ts +21 -0
  44. package/dist/cli/gate.js +179 -0
  45. package/dist/cli/impact-analysis.d.ts +63 -0
  46. package/dist/cli/impact-analysis.js +252 -0
  47. package/dist/cli/index.d.ts +9 -0
  48. package/dist/cli/index.js +332 -0
  49. package/dist/cli/init.d.ts +16 -0
  50. package/dist/cli/init.js +292 -0
  51. package/dist/cli/manifest.d.ts +103 -0
  52. package/dist/cli/manifest.js +282 -0
  53. package/dist/cli/migrate.d.ts +41 -0
  54. package/dist/cli/migrate.js +349 -0
  55. package/dist/cli/policy-packs.d.ts +23 -0
  56. package/dist/cli/policy-packs.js +89 -0
  57. package/dist/cli/print-config.d.ts +29 -0
  58. package/dist/cli/print-config.js +270 -0
  59. package/dist/cli/profiles.d.ts +28 -0
  60. package/dist/cli/profiles.js +30 -0
  61. package/dist/cli/reason-codes.d.ts +17 -0
  62. package/dist/cli/reason-codes.js +19 -0
  63. package/dist/cli/regression-gate.d.ts +15 -0
  64. package/dist/cli/regression-gate.js +341 -0
  65. package/dist/cli/render/snippet.d.ts +5 -0
  66. package/dist/cli/render/snippet.js +15 -0
  67. package/dist/cli/render/sort.d.ts +10 -0
  68. package/dist/cli/render/sort.js +24 -0
  69. package/dist/cli/report/build-check-report.d.ts +19 -0
  70. package/dist/cli/report/build-check-report.js +132 -0
  71. package/dist/cli/run.d.ts +101 -0
  72. package/dist/cli/run.js +395 -0
  73. package/dist/cli/share.d.ts +17 -0
  74. package/dist/cli/share.js +91 -0
  75. package/dist/cli/upgrade.d.ts +15 -0
  76. package/dist/cli/upgrade.js +492 -0
  77. package/dist/cli/workspace.d.ts +31 -0
  78. package/dist/cli/workspace.js +68 -0
  79. package/dist/client.d.ts +368 -0
  80. package/dist/client.js +893 -0
  81. package/dist/client.request.test.d.ts +1 -0
  82. package/dist/client.request.test.js +232 -0
  83. package/dist/context.d.ts +134 -0
  84. package/dist/context.js +215 -0
  85. package/dist/errors.d.ts +82 -0
  86. package/dist/errors.js +298 -0
  87. package/dist/export.d.ts +195 -0
  88. package/dist/export.js +344 -0
  89. package/dist/index.d.ts +44 -0
  90. package/dist/index.js +153 -0
  91. package/dist/integrations/anthropic.d.ts +91 -0
  92. package/dist/integrations/anthropic.js +163 -0
  93. package/dist/integrations/openai-eval.d.ts +57 -0
  94. package/dist/integrations/openai-eval.js +232 -0
  95. package/dist/integrations/openai.d.ts +92 -0
  96. package/dist/integrations/openai.js +160 -0
  97. package/dist/local.d.ts +39 -0
  98. package/dist/local.js +148 -0
  99. package/dist/logger.d.ts +128 -0
  100. package/dist/logger.js +227 -0
  101. package/dist/matchers/index.d.ts +1 -0
  102. package/dist/matchers/index.js +6 -0
  103. package/dist/matchers/to-pass-gate.d.ts +29 -0
  104. package/dist/matchers/to-pass-gate.js +35 -0
  105. package/dist/pagination.d.ts +74 -0
  106. package/dist/pagination.js +139 -0
  107. package/dist/regression.d.ts +100 -0
  108. package/dist/regression.js +44 -0
  109. package/dist/runtime/adapters/config-to-dsl.d.ts +33 -0
  110. package/dist/runtime/adapters/config-to-dsl.js +400 -0
  111. package/dist/runtime/adapters/testsuite-to-dsl.d.ts +63 -0
  112. package/dist/runtime/adapters/testsuite-to-dsl.js +276 -0
  113. package/dist/runtime/context.d.ts +26 -0
  114. package/dist/runtime/context.js +74 -0
  115. package/dist/runtime/eval.d.ts +46 -0
  116. package/dist/runtime/eval.js +244 -0
  117. package/dist/runtime/execution-mode.d.ts +80 -0
  118. package/dist/runtime/execution-mode.js +357 -0
  119. package/dist/runtime/executor.d.ts +16 -0
  120. package/dist/runtime/executor.js +152 -0
  121. package/dist/runtime/registry.d.ts +78 -0
  122. package/dist/runtime/registry.js +403 -0
  123. package/dist/runtime/run-report.d.ts +200 -0
  124. package/dist/runtime/run-report.js +222 -0
  125. package/dist/runtime/types.d.ts +356 -0
  126. package/dist/runtime/types.js +76 -0
  127. package/dist/snapshot.d.ts +176 -0
  128. package/dist/snapshot.js +322 -0
  129. package/dist/streaming.d.ts +173 -0
  130. package/dist/streaming.js +268 -0
  131. package/dist/testing.d.ts +273 -0
  132. package/dist/testing.js +317 -0
  133. package/dist/types.d.ts +754 -0
  134. package/dist/types.js +54 -0
  135. package/dist/utils/input-hash.d.ts +8 -0
  136. package/dist/utils/input-hash.js +41 -0
  137. package/dist/version.d.ts +7 -0
  138. package/dist/version.js +10 -0
  139. package/dist/workflows.d.ts +389 -0
  140. package/dist/workflows.js +671 -0
  141. package/package.json +117 -0
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * evalgate — EvalGate CLI
4
+ *
5
+ * Commands:
6
+ * evalgate init — Create evalgate.config.json
7
+ * evalgate check — CI/CD evaluation gate (see evalgate check --help)
8
+ */
9
+ export {};