@oddessentials/odd-ai-reviewers 1.10.1 → 1.12.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 (75) hide show
  1. package/README.md +16 -9
  2. package/dist/agents/control_flow/types.d.ts +1 -1
  3. package/dist/agents/control_flow/types.js +1 -1
  4. package/dist/agents/index.d.ts +1 -0
  5. package/dist/agents/index.d.ts.map +1 -1
  6. package/dist/agents/index.js +2 -0
  7. package/dist/agents/index.js.map +1 -1
  8. package/dist/benchmark/adapter.d.ts.map +1 -1
  9. package/dist/benchmark/adapter.js +4 -2
  10. package/dist/benchmark/adapter.js.map +1 -1
  11. package/dist/cache/store.d.ts.map +1 -1
  12. package/dist/cache/store.js +26 -2
  13. package/dist/cache/store.js.map +1 -1
  14. package/dist/cli/commands/local-review.d.ts +13 -2
  15. package/dist/cli/commands/local-review.d.ts.map +1 -1
  16. package/dist/cli/commands/local-review.js +164 -33
  17. package/dist/cli/commands/local-review.js.map +1 -1
  18. package/dist/cli/execution-plan.d.ts +118 -0
  19. package/dist/cli/execution-plan.d.ts.map +1 -0
  20. package/dist/cli/execution-plan.js +260 -0
  21. package/dist/cli/execution-plan.js.map +1 -0
  22. package/dist/config/schemas.d.ts +103 -21
  23. package/dist/config/schemas.d.ts.map +1 -1
  24. package/dist/config/schemas.js +177 -10
  25. package/dist/config/schemas.js.map +1 -1
  26. package/dist/config.d.ts +8 -3
  27. package/dist/config.d.ts.map +1 -1
  28. package/dist/config.js +15 -6
  29. package/dist/config.js.map +1 -1
  30. package/dist/main.d.ts.map +1 -1
  31. package/dist/main.js +79 -8
  32. package/dist/main.js.map +1 -1
  33. package/dist/phases/execute.d.ts +3 -0
  34. package/dist/phases/execute.d.ts.map +1 -1
  35. package/dist/phases/execute.js +17 -5
  36. package/dist/phases/execute.js.map +1 -1
  37. package/dist/phases/index.d.ts +1 -1
  38. package/dist/phases/index.d.ts.map +1 -1
  39. package/dist/phases/index.js +1 -1
  40. package/dist/phases/index.js.map +1 -1
  41. package/dist/phases/report.d.ts +28 -4
  42. package/dist/phases/report.d.ts.map +1 -1
  43. package/dist/phases/report.js +86 -36
  44. package/dist/phases/report.js.map +1 -1
  45. package/dist/report/ado.d.ts +2 -1
  46. package/dist/report/ado.d.ts.map +1 -1
  47. package/dist/report/ado.js +9 -5
  48. package/dist/report/ado.js.map +1 -1
  49. package/dist/report/finding-validator.d.ts +1 -4
  50. package/dist/report/finding-validator.d.ts.map +1 -1
  51. package/dist/report/finding-validator.js +23 -54
  52. package/dist/report/finding-validator.js.map +1 -1
  53. package/dist/report/framework-pattern-filter.d.ts +1 -1
  54. package/dist/report/framework-pattern-filter.d.ts.map +1 -1
  55. package/dist/report/framework-pattern-filter.js +114 -99
  56. package/dist/report/framework-pattern-filter.js.map +1 -1
  57. package/dist/report/github.d.ts +2 -1
  58. package/dist/report/github.d.ts.map +1 -1
  59. package/dist/report/github.js +9 -5
  60. package/dist/report/github.js.map +1 -1
  61. package/dist/report/terminal.d.ts +42 -4
  62. package/dist/report/terminal.d.ts.map +1 -1
  63. package/dist/report/terminal.js +36 -8
  64. package/dist/report/terminal.js.map +1 -1
  65. package/dist/report/user-suppressions.d.ts +74 -0
  66. package/dist/report/user-suppressions.d.ts.map +1 -0
  67. package/dist/report/user-suppressions.js +264 -0
  68. package/dist/report/user-suppressions.js.map +1 -0
  69. package/dist/security-logger.d.ts +1 -1
  70. package/dist/security-logger.js +1 -1
  71. package/package.json +7 -6
  72. package/dist/__tests__/hermetic-setup.d.ts +0 -55
  73. package/dist/__tests__/hermetic-setup.d.ts.map +0 -1
  74. package/dist/__tests__/hermetic-setup.js +0 -62
  75. package/dist/__tests__/hermetic-setup.js.map +0 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * User-Configurable Suppressions (FR-022)
3
+ *
4
+ * Stage 1.25 in the finding pipeline: applies user-defined suppression rules
5
+ * from .ai-review.yml configuration. Runs after semantic validation (Stage 1)
6
+ * and before framework convention filter (Stage 1.5).
7
+ *
8
+ * Security: In CI mode, rules are loaded from the BASE branch config to prevent
9
+ * attackers from smuggling suppressions into fork PRs.
10
+ */
11
+ import type { Finding } from '../agents/types.js';
12
+ import { type SuppressionRule, type Suppressions } from '../config/schemas.js';
13
+ export interface SuppressionMatchResult {
14
+ finding: Finding;
15
+ rule: SuppressionRule;
16
+ ruleIndex: number;
17
+ }
18
+ export interface UserSuppressionResult {
19
+ /** Findings that passed all suppression rules */
20
+ filtered: Finding[];
21
+ /** Findings that were suppressed with their matching rule */
22
+ suppressed: SuppressionMatchResult[];
23
+ /** Per-rule match count (keyed by rule index) */
24
+ matchCounts: Map<number, number>;
25
+ }
26
+ export type SuppressionMode = 'ci' | 'local';
27
+ export interface BreadthViolation {
28
+ ruleIndex: number;
29
+ reason: string;
30
+ matchCount: number;
31
+ limit: number;
32
+ hasOverride: boolean;
33
+ }
34
+ /**
35
+ * Filter findings through user-defined suppression rules.
36
+ * First matching rule wins (no multi-rule accumulation).
37
+ */
38
+ export declare function filterUserSuppressions(findings: Finding[], rules: SuppressionRule[]): UserSuppressionResult;
39
+ /**
40
+ * Check breadth limits on suppression match counts.
41
+ * Returns violations that should cause failures (CI) or warnings (local).
42
+ */
43
+ export declare function checkBreadthLimits(rules: SuppressionRule[], matchCounts: Map<number, number>): BreadthViolation[];
44
+ /**
45
+ * Check if a breadth-override rule is authorized to suppress error-severity findings.
46
+ * Returns violations for unauthorized error-severity suppressions.
47
+ */
48
+ export declare function checkErrorSeverityOverrides(rules: SuppressionRule[], suppressedResults: SuppressionMatchResult[], securityOverrideAllowlist: string[]): BreadthViolation[];
49
+ /**
50
+ * Enforce breadth limits based on mode.
51
+ * In CI mode: throws Error for violations.
52
+ * In local mode: logs warnings only.
53
+ */
54
+ export declare function enforceBreadthLimits(rules: SuppressionRule[], result: UserSuppressionResult, mode: SuppressionMode, securityOverrideAllowlist: string[]): void;
55
+ /**
56
+ * Build suppression match count summary for JSON output.
57
+ */
58
+ export declare function buildSuppressionSummary(rules: SuppressionRule[], matchCounts: Map<number, number>): {
59
+ reason: string;
60
+ matched: number;
61
+ }[];
62
+ /**
63
+ * Load suppressions from the base branch config via `git show`.
64
+ *
65
+ * Security (FR-022): In CI mode, suppression rules MUST be loaded from the
66
+ * BASE branch configuration only, never from the PR branch. This prevents
67
+ * attackers from smuggling suppressions into fork PRs to hide vulnerabilities.
68
+ *
69
+ * @param repoPath - Path to the git repository
70
+ * @param baseRef - Base branch ref (e.g., 'origin/main', a SHA)
71
+ * @returns Parsed suppressions, or empty defaults if base has no config/suppressions
72
+ */
73
+ export declare function loadBaseBranchSuppressions(repoPath: string, baseRef: string): Suppressions;
74
+ //# sourceMappingURL=user-suppressions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-suppressions.d.ts","sourceRoot":"","sources":["../../src/report/user-suppressions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAsB,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AASnG,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,6DAA6D;IAC7D,UAAU,EAAE,sBAAsB,EAAE,CAAC;IACrC,iDAAiD;IACjD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB;AA4CD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,eAAe,EAAE,GACvB,qBAAqB,CAqCvB;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EAAE,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,gBAAgB,EAAE,CAuBpB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,eAAe,EAAE,EACxB,iBAAiB,EAAE,sBAAsB,EAAE,EAC3C,yBAAyB,EAAE,MAAM,EAAE,GAClC,gBAAgB,EAAE,CA8BpB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,eAAe,EAAE,EACxB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,eAAe,EACrB,yBAAyB,EAAE,MAAM,EAAE,GAClC,IAAI,CA0DN;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,eAAe,EAAE,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAUvC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAsD1F"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * User-Configurable Suppressions (FR-022)
3
+ *
4
+ * Stage 1.25 in the finding pipeline: applies user-defined suppression rules
5
+ * from .ai-review.yml configuration. Runs after semantic validation (Stage 1)
6
+ * and before framework convention filter (Stage 1.5).
7
+ *
8
+ * Security: In CI mode, rules are loaded from the BASE branch config to prevent
9
+ * attackers from smuggling suppressions into fork PRs.
10
+ */
11
+ import { execFileSync } from 'child_process';
12
+ import { parse as parseYaml } from 'yaml';
13
+ import { minimatch } from 'minimatch';
14
+ import { SuppressionsSchema } from '../config/schemas.js';
15
+ import { ConfigError, ConfigErrorCode } from '../types/errors.js';
16
+ import { SafeGitRefHelpers } from '../types/branded.js';
17
+ import { isOk } from '../types/result.js';
18
+ // =============================================================================
19
+ // Matching Logic
20
+ // =============================================================================
21
+ /**
22
+ * Test if a suppression rule matches a finding.
23
+ * All specified criteria must match (AND logic).
24
+ */
25
+ function ruleMatchesFinding(rule, finding) {
26
+ // Rule ID: glob match
27
+ if (rule.rule !== undefined) {
28
+ const ruleId = finding.ruleId ?? '';
29
+ if (!minimatch(ruleId, rule.rule))
30
+ return false;
31
+ }
32
+ // Message: anchored regex (validated at config load: must start with ^ and end with $)
33
+ if (rule.message !== undefined) {
34
+ try {
35
+ // SAFETY: rule.message is validated by SuppressionRuleSchema to be fully anchored (^...$).
36
+ // eslint-disable-next-line security/detect-non-literal-regexp
37
+ const regex = new RegExp(rule.message);
38
+ if (!regex.test(finding.message))
39
+ return false;
40
+ }
41
+ catch {
42
+ // Invalid regex — treat as non-match (validated at config time)
43
+ return false;
44
+ }
45
+ }
46
+ // File: glob match
47
+ if (rule.file !== undefined) {
48
+ const filePath = finding.file ?? '';
49
+ if (!minimatch(filePath, rule.file))
50
+ return false;
51
+ }
52
+ // Severity: exact match
53
+ if (rule.severity !== undefined) {
54
+ if (finding.severity !== rule.severity)
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ /**
60
+ * Filter findings through user-defined suppression rules.
61
+ * First matching rule wins (no multi-rule accumulation).
62
+ */
63
+ export function filterUserSuppressions(findings, rules) {
64
+ if (rules.length === 0) {
65
+ return { filtered: [...findings], suppressed: [], matchCounts: new Map() };
66
+ }
67
+ const filtered = [];
68
+ const suppressed = [];
69
+ const matchCounts = new Map();
70
+ // Initialize counts
71
+ for (let i = 0; i < rules.length; i++) {
72
+ matchCounts.set(i, 0);
73
+ }
74
+ for (const finding of findings) {
75
+ let wasSuppressed = false;
76
+ for (let i = 0; i < rules.length; i++) {
77
+ const rule = rules[i];
78
+ if (ruleMatchesFinding(rule, finding)) {
79
+ suppressed.push({ finding, rule, ruleIndex: i });
80
+ matchCounts.set(i, (matchCounts.get(i) ?? 0) + 1);
81
+ wasSuppressed = true;
82
+ // Diagnostics go to stderr to avoid corrupting JSON/SARIF stdout output
83
+ console.error(`[router] [user-suppression] Suppressed: ${finding.file}:${finding.line ?? '?'} — rule: "${rule.reason}"`);
84
+ break; // First matching rule wins
85
+ }
86
+ }
87
+ if (!wasSuppressed) {
88
+ filtered.push(finding);
89
+ }
90
+ }
91
+ return { filtered, suppressed, matchCounts };
92
+ }
93
+ // =============================================================================
94
+ // Breadth Enforcement
95
+ // =============================================================================
96
+ const DEFAULT_BREADTH_LIMIT = 20;
97
+ const OVERRIDE_BREADTH_LIMIT = 200;
98
+ /**
99
+ * Check breadth limits on suppression match counts.
100
+ * Returns violations that should cause failures (CI) or warnings (local).
101
+ */
102
+ export function checkBreadthLimits(rules, matchCounts) {
103
+ const violations = [];
104
+ for (let i = 0; i < rules.length; i++) {
105
+ const rule = rules[i];
106
+ const count = matchCounts.get(i) ?? 0;
107
+ if (count === 0)
108
+ continue;
109
+ const hasOverride = rule.breadth_override === true;
110
+ const limit = hasOverride ? OVERRIDE_BREADTH_LIMIT : DEFAULT_BREADTH_LIMIT;
111
+ if (count > limit) {
112
+ violations.push({
113
+ ruleIndex: i,
114
+ reason: rule.reason,
115
+ matchCount: count,
116
+ limit,
117
+ hasOverride,
118
+ });
119
+ }
120
+ }
121
+ return violations;
122
+ }
123
+ /**
124
+ * Check if a breadth-override rule is authorized to suppress error-severity findings.
125
+ * Returns violations for unauthorized error-severity suppressions.
126
+ */
127
+ export function checkErrorSeverityOverrides(rules, suppressedResults, securityOverrideAllowlist) {
128
+ const violations = [];
129
+ // Group suppressed findings by rule index
130
+ const suppressedByRule = new Map();
131
+ for (const result of suppressedResults) {
132
+ const existing = suppressedByRule.get(result.ruleIndex) ?? [];
133
+ existing.push(result);
134
+ suppressedByRule.set(result.ruleIndex, existing);
135
+ }
136
+ for (let i = 0; i < rules.length; i++) {
137
+ const rule = rules[i];
138
+ if (!rule.breadth_override)
139
+ continue;
140
+ const ruleSuppressions = suppressedByRule.get(i) ?? [];
141
+ const hasErrorSeverity = ruleSuppressions.some((s) => s.finding.severity === 'error');
142
+ if (hasErrorSeverity && !securityOverrideAllowlist.includes(rule.reason)) {
143
+ violations.push({
144
+ ruleIndex: i,
145
+ reason: rule.reason,
146
+ matchCount: ruleSuppressions.length,
147
+ limit: DEFAULT_BREADTH_LIMIT,
148
+ hasOverride: true,
149
+ });
150
+ }
151
+ }
152
+ return violations;
153
+ }
154
+ /**
155
+ * Enforce breadth limits based on mode.
156
+ * In CI mode: throws Error for violations.
157
+ * In local mode: logs warnings only.
158
+ */
159
+ export function enforceBreadthLimits(rules, result, mode, securityOverrideAllowlist) {
160
+ const breadthViolations = checkBreadthLimits(rules, result.matchCounts);
161
+ const errorSeverityViolations = checkErrorSeverityOverrides(rules, result.suppressed, securityOverrideAllowlist);
162
+ const allViolations = [...breadthViolations, ...errorSeverityViolations];
163
+ if (allViolations.length === 0) {
164
+ // Log overrides that are within limits
165
+ for (let i = 0; i < rules.length; i++) {
166
+ const rule = rules[i];
167
+ const count = result.matchCounts.get(i) ?? 0;
168
+ if (rule.breadth_override && count > DEFAULT_BREADTH_LIMIT) {
169
+ console.error(`[router] [user-suppression] Broad suppression override: '${rule.reason}' approved by ${rule.approved_by ?? 'unknown'} — matched ${count} findings`);
170
+ }
171
+ }
172
+ return;
173
+ }
174
+ if (mode === 'local') {
175
+ for (const v of allViolations) {
176
+ console.warn(`[router] [user-suppression] Warning: Suppression rule '${v.reason}' matched ${v.matchCount} findings (limit: ${v.limit})`);
177
+ }
178
+ return;
179
+ }
180
+ // CI mode: fail on violations — error-severity violations take precedence
181
+ // FR-022: Breadth violations produce exit code 2 (config_error) via ConfigError
182
+ if (errorSeverityViolations.length > 0) {
183
+ const v = errorSeverityViolations[0];
184
+ throw new ConfigError(`Breadth override on rule '${v.reason}' cannot suppress error-severity findings — add to security_override_allowlist to authorize`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
185
+ }
186
+ const firstViolation = allViolations[0];
187
+ if (firstViolation.hasOverride) {
188
+ throw new ConfigError(`Suppression rule '${firstViolation.reason}' matched ${firstViolation.matchCount} findings (limit: ${firstViolation.limit}). Override limit exceeded.`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
189
+ }
190
+ throw new ConfigError(`Suppression rule '${firstViolation.reason}' matched ${firstViolation.matchCount} findings (limit: ${firstViolation.limit}). Add \`breadth_override: true\` to this rule to allow broad suppression.`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
191
+ }
192
+ /**
193
+ * Build suppression match count summary for JSON output.
194
+ */
195
+ export function buildSuppressionSummary(rules, matchCounts) {
196
+ const summary = [];
197
+ for (let i = 0; i < rules.length; i++) {
198
+ const rule = rules[i];
199
+ const count = matchCounts.get(i) ?? 0;
200
+ if (count > 0) {
201
+ summary.push({ reason: rule.reason, matched: count });
202
+ }
203
+ }
204
+ return summary;
205
+ }
206
+ // =============================================================================
207
+ // Base-Branch Suppression Loading (CI Security)
208
+ // =============================================================================
209
+ /**
210
+ * Load suppressions from the base branch config via `git show`.
211
+ *
212
+ * Security (FR-022): In CI mode, suppression rules MUST be loaded from the
213
+ * BASE branch configuration only, never from the PR branch. This prevents
214
+ * attackers from smuggling suppressions into fork PRs to hide vulnerabilities.
215
+ *
216
+ * @param repoPath - Path to the git repository
217
+ * @param baseRef - Base branch ref (e.g., 'origin/main', a SHA)
218
+ * @returns Parsed suppressions, or empty defaults if base has no config/suppressions
219
+ */
220
+ export function loadBaseBranchSuppressions(repoPath, baseRef) {
221
+ const emptySuppressions = {
222
+ rules: [],
223
+ disable_matchers: [],
224
+ security_override_allowlist: [],
225
+ };
226
+ // Defense-in-depth: validate baseRef before passing to git
227
+ const refResult = SafeGitRefHelpers.parse(baseRef);
228
+ if (!isOk(refResult)) {
229
+ console.warn(`[router] [user-suppression] Invalid base ref, skipping suppression loading: ${baseRef}`);
230
+ return emptySuppressions;
231
+ }
232
+ try {
233
+ const configContent = execFileSync('git', ['show', `${refResult.value}:.ai-review.yml`], {
234
+ cwd: repoPath,
235
+ encoding: 'utf-8',
236
+ stdio: ['pipe', 'pipe', 'pipe'],
237
+ timeout: 10000,
238
+ });
239
+ const parsed = parseYaml(configContent);
240
+ if (!parsed || typeof parsed !== 'object' || !('suppressions' in parsed)) {
241
+ console.error('[router] [user-suppression] Base branch config has no suppressions section');
242
+ return emptySuppressions;
243
+ }
244
+ const result = SuppressionsSchema.safeParse(parsed['suppressions']);
245
+ if (!result.success) {
246
+ console.warn(`[router] [user-suppression] Base branch suppressions invalid, ignoring: ${result.error.message}`);
247
+ return emptySuppressions;
248
+ }
249
+ console.error(`[router] [user-suppression] Loaded ${result.data.rules.length} suppression rule(s) from base branch (${baseRef})`);
250
+ return result.data;
251
+ }
252
+ catch (error) {
253
+ // git show fails when config doesn't exist on base branch — expected
254
+ const message = error instanceof Error ? error.message : String(error);
255
+ if (message.includes('does not exist') || message.includes('fatal:')) {
256
+ console.error('[router] [user-suppression] No .ai-review.yml on base branch — no suppressions active');
257
+ }
258
+ else {
259
+ console.warn(`[router] [user-suppression] Failed to load base branch config: ${message}`);
260
+ }
261
+ return emptySuppressions;
262
+ }
263
+ }
264
+ //# sourceMappingURL=user-suppressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-suppressions.js","sourceRoot":"","sources":["../../src/report/user-suppressions.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  * - Consistent structured format across all events
11
11
  *
12
12
  * @see FR-021, FR-022, FR-023, FR-024
13
- * @see specs/006-quality-enforcement/contracts/security-event.ts
13
+ * @see specs/archive/006-quality-enforcement/contracts/security-event.ts
14
14
  */
15
15
  import { z } from 'zod';
16
16
  /**
@@ -10,7 +10,7 @@
10
10
  * - Consistent structured format across all events
11
11
  *
12
12
  * @see FR-021, FR-022, FR-023, FR-024
13
- * @see specs/006-quality-enforcement/contracts/security-event.ts
13
+ * @see specs/archive/006-quality-enforcement/contracts/security-event.ts
14
14
  */
15
15
  import { createHash } from 'crypto';
16
16
  import { z } from 'zod';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oddessentials/odd-ai-reviewers",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "AI-powered code review CLI - run locally or in CI/CD pipelines",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -32,19 +32,19 @@
32
32
  "homepage": "https://github.com/oddessentials/odd-ai-reviewers#readme",
33
33
  "dependencies": {
34
34
  "@actions/cache": "5.0.5",
35
- "@anthropic-ai/sdk": "0.71.2",
35
+ "@anthropic-ai/sdk": "0.78.0",
36
36
  "@octokit/rest": "^22.0.1",
37
37
  "commander": "^14.0.3",
38
38
  "minimatch": "^10.2.4",
39
- "openai": "^6.27.0",
39
+ "openai": "^6.29.0",
40
40
  "typescript": "5.9.3",
41
41
  "yaml": "^2.8.2",
42
42
  "zod": "^4.3.6"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "25.4.0",
46
- "@vitest/coverage-v8": "4.0.18",
47
- "vitest": "^4.0.18"
45
+ "@types/node": "25.5.0",
46
+ "@vitest/coverage-v8": "4.1.0",
47
+ "vitest": "^4.1.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.0.0"
@@ -58,6 +58,7 @@
58
58
  "test:coverage": "vitest run --coverage",
59
59
  "test:ci": "vitest run --reporter=json --outputFile=test-results.json",
60
60
  "test:ci:coverage": "vitest run --coverage --reporter=json --reporter=dot --outputFile=test-results.json",
61
+ "test:ci-thresholds": "CI=true vitest run --coverage",
61
62
  "typecheck": "tsc --noEmit"
62
63
  }
63
64
  }
@@ -1,55 +0,0 @@
1
- /**
2
- * Hermetic Test Setup Utilities
3
- *
4
- * Shared test infrastructure for deterministic, isolated tests.
5
- * Located in __tests__/ directory which is classified as test code
6
- * by dependency-cruiser, allowing legitimate vitest imports.
7
- *
8
- * Provides:
9
- * - Frozen time (no wall-clock dependencies)
10
- * - Deterministic teardown
11
- *
12
- * @example
13
- * ```typescript
14
- * import { describe, it, beforeEach, afterEach } from 'vitest';
15
- * import {
16
- * FROZEN_TIMESTAMP,
17
- * setupHermeticTest,
18
- * teardownHermeticTest,
19
- * } from '../hermetic-setup.js';
20
- *
21
- * describe('MyFeature', () => {
22
- * beforeEach(() => setupHermeticTest());
23
- * afterEach(() => teardownHermeticTest());
24
- *
25
- * it('works with frozen time', () => {
26
- * expect(new Date().toISOString()).toBe(FROZEN_TIMESTAMP);
27
- * });
28
- * });
29
- * ```
30
- */
31
- /**
32
- * Frozen test timestamp - use consistently across all hermetic tests
33
- */
34
- export declare const FROZEN_TIMESTAMP = "2026-01-29T00:00:00.000Z";
35
- export declare const FROZEN_DATE: Date;
36
- /**
37
- * Setup hermetic test environment
38
- *
39
- * Configures:
40
- * - Frozen system time to FROZEN_TIMESTAMP
41
- *
42
- * Call this in beforeEach()
43
- */
44
- export declare function setupHermeticTest(): void;
45
- /**
46
- * Teardown hermetic test environment
47
- *
48
- * Restores:
49
- * - Real system time
50
- * - All mocks
51
- *
52
- * Call this in afterEach()
53
- */
54
- export declare function teardownHermeticTest(): void;
55
- //# sourceMappingURL=hermetic-setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hermetic-setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/hermetic-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH;;GAEG;AACH,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAC3D,eAAO,MAAM,WAAW,MAA6B,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
@@ -1,62 +0,0 @@
1
- /**
2
- * Hermetic Test Setup Utilities
3
- *
4
- * Shared test infrastructure for deterministic, isolated tests.
5
- * Located in __tests__/ directory which is classified as test code
6
- * by dependency-cruiser, allowing legitimate vitest imports.
7
- *
8
- * Provides:
9
- * - Frozen time (no wall-clock dependencies)
10
- * - Deterministic teardown
11
- *
12
- * @example
13
- * ```typescript
14
- * import { describe, it, beforeEach, afterEach } from 'vitest';
15
- * import {
16
- * FROZEN_TIMESTAMP,
17
- * setupHermeticTest,
18
- * teardownHermeticTest,
19
- * } from '../hermetic-setup.js';
20
- *
21
- * describe('MyFeature', () => {
22
- * beforeEach(() => setupHermeticTest());
23
- * afterEach(() => teardownHermeticTest());
24
- *
25
- * it('works with frozen time', () => {
26
- * expect(new Date().toISOString()).toBe(FROZEN_TIMESTAMP);
27
- * });
28
- * });
29
- * ```
30
- */
31
- import { vi } from 'vitest';
32
- /**
33
- * Frozen test timestamp - use consistently across all hermetic tests
34
- */
35
- export const FROZEN_TIMESTAMP = '2026-01-29T00:00:00.000Z';
36
- export const FROZEN_DATE = new Date(FROZEN_TIMESTAMP);
37
- /**
38
- * Setup hermetic test environment
39
- *
40
- * Configures:
41
- * - Frozen system time to FROZEN_TIMESTAMP
42
- *
43
- * Call this in beforeEach()
44
- */
45
- export function setupHermeticTest() {
46
- vi.useFakeTimers();
47
- vi.setSystemTime(FROZEN_DATE);
48
- }
49
- /**
50
- * Teardown hermetic test environment
51
- *
52
- * Restores:
53
- * - Real system time
54
- * - All mocks
55
- *
56
- * Call this in afterEach()
57
- */
58
- export function teardownHermeticTest() {
59
- vi.useRealTimers();
60
- vi.restoreAllMocks();
61
- }
62
- //# sourceMappingURL=hermetic-setup.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hermetic-setup.js","sourceRoot":"","sources":["../../src/__tests__/hermetic-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,EAAE,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB;IAClC,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC"}