@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,260 @@
1
+ /**
2
+ * Execution Plan Module (FR-001 through FR-007)
3
+ *
4
+ * Produces a single, immutable ExecutionPlan object that is the sole source of truth
5
+ * for all downstream code paths (dry-run, cost-only, dependency check, execution).
6
+ *
7
+ * Pipeline: Parse -> Validate -> BuildExecutionPlan -> DependencyCheck -> Execute
8
+ *
9
+ * No downstream consumer may read raw CLI flags — they operate on the plan.
10
+ */
11
+ import { AGENT_REGISTRY, getAgentById, getCompatibleAgents, } from '../config/schemas.js';
12
+ import { ConfigError, ConfigErrorCode } from '../types/errors.js';
13
+ import { assertNever } from '../types/assert-never.js';
14
+ /**
15
+ * The ONLY path to produce an exit code.
16
+ * No call site may hardcode an exit code number directly.
17
+ */
18
+ export function exitCodeFromStatus(status) {
19
+ switch (status) {
20
+ case 'complete':
21
+ return 0;
22
+ case 'gating_failed':
23
+ return 1;
24
+ case 'config_error':
25
+ return 2;
26
+ case 'incomplete':
27
+ return 3;
28
+ default:
29
+ return assertNever(status);
30
+ }
31
+ }
32
+ // =============================================================================
33
+ // Validation Helpers
34
+ // =============================================================================
35
+ /**
36
+ * Validate --pass flag against configured passes.
37
+ * Throws ConfigError if unknown.
38
+ */
39
+ function validatePassFilter(passFilter, passes) {
40
+ const match = passes.find((p) => p.name === passFilter);
41
+ if (!match) {
42
+ const available = passes.map((p) => p.name).join(', ');
43
+ throw new ConfigError(`Unknown pass '${passFilter}'. Available: ${available}`, ConfigErrorCode.INVALID_VALUE, { field: 'pass', expected: available, actual: passFilter });
44
+ }
45
+ if (!match.enabled) {
46
+ throw new ConfigError(`Pass '${passFilter}' is disabled in configuration`, ConfigErrorCode.INVALID_VALUE, { field: 'pass', actual: passFilter });
47
+ }
48
+ return match;
49
+ }
50
+ /**
51
+ * Validate --agent flag against the agent registry.
52
+ * Throws ConfigError if unknown.
53
+ */
54
+ function validateAgentFilter(agentFilter) {
55
+ const entry = getAgentById(agentFilter);
56
+ if (!entry) {
57
+ const validIds = AGENT_REGISTRY.map((a) => a.id).join(', ');
58
+ throw new ConfigError(`Unknown agent '${agentFilter}'. Valid: ${validIds}`, ConfigErrorCode.INVALID_VALUE, { field: 'agent', expected: validIds, actual: agentFilter });
59
+ }
60
+ return entry;
61
+ }
62
+ /**
63
+ * Validate pass composition at config time.
64
+ * Checks for unknown agents and duplicates within passes.
65
+ */
66
+ function validatePassComposition(passes) {
67
+ for (const pass of passes) {
68
+ // Check for unknown agents
69
+ for (const agentId of pass.agents) {
70
+ if (!getAgentById(agentId)) {
71
+ const validIds = AGENT_REGISTRY.map((a) => a.id).join(', ');
72
+ throw new ConfigError(`Unknown agent '${agentId}' in pass '${pass.name}'. Valid: ${validIds}`, ConfigErrorCode.INVALID_VALUE, { field: `passes.${pass.name}.agents`, expected: validIds, actual: agentId });
73
+ }
74
+ }
75
+ // Check for duplicates
76
+ const seen = new Set();
77
+ for (const agentId of pass.agents) {
78
+ if (seen.has(agentId)) {
79
+ throw new ConfigError(`Duplicate agent '${agentId}' in pass '${pass.name}'`, ConfigErrorCode.INVALID_VALUE, { field: `passes.${pass.name}.agents`, actual: agentId });
80
+ }
81
+ seen.add(agentId);
82
+ }
83
+ }
84
+ }
85
+ // =============================================================================
86
+ // Build Execution Plan
87
+ // =============================================================================
88
+ /**
89
+ * Build an immutable execution plan from config and CLI options.
90
+ *
91
+ * Enforces all invariants:
92
+ * - Pass/agent validation against registry
93
+ * - Provider compatibility filtering
94
+ * - Empty-pass rule (required+empty -> ConfigError; optional+empty -> skipped)
95
+ * - Combined --pass + --agent narrowing (FR-007)
96
+ *
97
+ * @throws ConfigError on validation failures (exit code 2)
98
+ */
99
+ export function buildExecutionPlan(options) {
100
+ const { config, mode, passFilter, agentFilter, provider, model, configSource } = options;
101
+ // 1. Validate pass composition at config time
102
+ validatePassComposition(config.passes);
103
+ // 2. Start with enabled passes
104
+ let candidatePasses = config.passes.filter((p) => p.enabled);
105
+ // 3. Apply --pass filter
106
+ if (passFilter) {
107
+ const matchedPass = validatePassFilter(passFilter, config.passes);
108
+ candidatePasses = [matchedPass];
109
+ }
110
+ // 4. Validate --agent filter against registry
111
+ if (agentFilter) {
112
+ validateAgentFilter(agentFilter);
113
+ }
114
+ // 5. Get compatible agents for the provider
115
+ const compatibleAgents = getCompatibleAgents(provider);
116
+ const compatibleIds = new Set(compatibleAgents.map((a) => a.id));
117
+ const requestedAgentId = agentFilter;
118
+ // 6. Build planned passes with agent filtering
119
+ const plannedPasses = [];
120
+ const skippedPasses = [];
121
+ for (const pass of candidatePasses) {
122
+ // Filter agents by provider compatibility
123
+ let agents = pass.agents.filter((id) => compatibleIds.has(id));
124
+ // Log provider-incompatible agents as excluded (non-required passes only)
125
+ const excludedByProvider = pass.agents.filter((id) => !compatibleIds.has(id));
126
+ if (excludedByProvider.length > 0 && pass.required) {
127
+ // Required pass with incompatible agents -> config error
128
+ const incompatible = excludedByProvider.join(', ');
129
+ throw new ConfigError(`Required pass '${pass.name}' contains agents incompatible with provider '${provider}': ${incompatible}`, ConfigErrorCode.INVALID_VALUE, { field: `passes.${pass.name}.agents`, actual: incompatible });
130
+ }
131
+ // Apply --agent filter within pass
132
+ if (agentFilter) {
133
+ const passContainsRequestedAgent = pass.agents.includes(requestedAgentId);
134
+ if (passFilter &&
135
+ passContainsRequestedAgent &&
136
+ !compatibleIds.has(requestedAgentId)) {
137
+ throw new ConfigError(`Agent '${agentFilter}' in pass '${passFilter}' is incompatible with provider '${provider}'`, ConfigErrorCode.INVALID_VALUE, { field: 'agent', actual: agentFilter });
138
+ }
139
+ const hasAgent = agents.some((id) => id === agentFilter);
140
+ if (hasAgent) {
141
+ agents = agents.filter((id) => id === agentFilter);
142
+ }
143
+ else if (passFilter) {
144
+ // Combined: --pass + --agent, agent not in the selected pass
145
+ const otherPasses = config.passes
146
+ .filter((p) => p.enabled && p.agents.includes(agentFilter))
147
+ .map((p) => p.name);
148
+ const availableIn = otherPasses.length > 0 ? ` It is available in: ${otherPasses.join(', ')}` : '';
149
+ throw new ConfigError(`Agent '${agentFilter}' is not configured in pass '${passFilter}'.${availableIn}`, ConfigErrorCode.INVALID_VALUE, { field: 'agent', actual: agentFilter });
150
+ }
151
+ else {
152
+ // Agent not in this pass, skip it (will be caught by check below if in no pass)
153
+ agents = [];
154
+ }
155
+ }
156
+ // Empty-pass rule
157
+ if (agents.length === 0) {
158
+ if (pass.required) {
159
+ throw new ConfigError(`Required pass '${pass.name}' has no runnable agents after filtering`, ConfigErrorCode.INVALID_VALUE, { field: `passes.${pass.name}` });
160
+ }
161
+ const reason = excludedByProvider.length > 0
162
+ ? `Pass '${pass.name}' skipped: no agents compatible with provider '${provider}'`
163
+ : `Pass '${pass.name}' skipped: no matching agents after filtering`;
164
+ skippedPasses.push({ name: pass.name, reason });
165
+ continue;
166
+ }
167
+ plannedPasses.push({
168
+ name: pass.name,
169
+ agents: agents,
170
+ required: pass.required,
171
+ });
172
+ }
173
+ // 7. If --agent was specified but found in no pass, error
174
+ if (agentFilter && !passFilter && plannedPasses.length === 0) {
175
+ // Check if the agent exists in any configured pass at all
176
+ const passesWithAgent = config.passes
177
+ .filter((p) => p.agents.includes(agentFilter))
178
+ .map((p) => p.name);
179
+ if (passesWithAgent.length === 0) {
180
+ throw new ConfigError(`Agent '${agentFilter}' not configured in any pass`, ConfigErrorCode.INVALID_VALUE, { field: 'agent', actual: agentFilter });
181
+ }
182
+ if (!compatibleIds.has(agentFilter)) {
183
+ throw new ConfigError(`Agent '${agentFilter}' is incompatible with provider '${provider}' and cannot run in configured passes: ${passesWithAgent.join(', ')}`, ConfigErrorCode.INVALID_VALUE, { field: 'agent', actual: agentFilter });
184
+ }
185
+ }
186
+ // 8. Assert structural invariant: no pass with empty agents
187
+ for (const pass of plannedPasses) {
188
+ if (pass.agents.length === 0) {
189
+ throw new Error(`Invariant violation: pass '${pass.name}' has empty agents list in execution plan`);
190
+ }
191
+ }
192
+ // 9. Build limits snapshot
193
+ const limits = {
194
+ maxDiffLines: config.limits.max_diff_lines,
195
+ maxFiles: config.limits.max_files,
196
+ maxTokensPerPr: config.limits.max_tokens_per_pr,
197
+ maxUsdPerPr: config.limits.max_usd_per_pr,
198
+ };
199
+ // 10. Build gating snapshot
200
+ const gating = {
201
+ enabled: config.gating.enabled,
202
+ failOnSeverity: config.gating.fail_on_severity,
203
+ driftGate: config.gating.drift_gate,
204
+ };
205
+ const plan = {
206
+ mode,
207
+ passes: plannedPasses,
208
+ skippedPasses,
209
+ provider: provider ?? null,
210
+ model: model ?? null,
211
+ limits,
212
+ gating,
213
+ configSource,
214
+ schemaVersion: config.version,
215
+ };
216
+ return plan;
217
+ }
218
+ // =============================================================================
219
+ // Plan Serialization (Canonical, Redacted)
220
+ // =============================================================================
221
+ /**
222
+ * Safe-field allowlist for plan serialization.
223
+ * Only these fields appear in serialized output — everything else is excluded.
224
+ * Keys are emitted in alphabetical order at every level.
225
+ */
226
+ export function serializeExecutionPlan(plan) {
227
+ const canonical = {
228
+ configSource: plan.configSource,
229
+ gating: {
230
+ driftGate: plan.gating.driftGate,
231
+ enabled: plan.gating.enabled,
232
+ failOnSeverity: plan.gating.failOnSeverity,
233
+ },
234
+ limits: {
235
+ maxDiffLines: plan.limits.maxDiffLines,
236
+ maxFiles: plan.limits.maxFiles,
237
+ maxTokensPerPr: plan.limits.maxTokensPerPr,
238
+ maxUsdPerPr: plan.limits.maxUsdPerPr,
239
+ },
240
+ mode: plan.mode,
241
+ model: plan.model,
242
+ passes: plan.passes.map((p) => ({
243
+ agents: [...p.agents].sort(),
244
+ name: p.name,
245
+ required: p.required,
246
+ })),
247
+ provider: plan.provider,
248
+ schemaVersion: plan.schemaVersion,
249
+ ...(plan.skippedPasses.length > 0
250
+ ? {
251
+ skippedPasses: plan.skippedPasses.map((s) => ({
252
+ name: s.name,
253
+ reason: s.reason,
254
+ })),
255
+ }
256
+ : {}),
257
+ };
258
+ return JSON.stringify(canonical, null, 2);
259
+ }
260
+ //# sourceMappingURL=execution-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-plan.js","sourceRoot":"","sources":["../../src/cli/execution-plan.ts"],"names":[],"mappings":"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"}
@@ -5,25 +5,41 @@
5
5
  * Extracted from config.ts to improve modularity.
6
6
  */
7
7
  import { z } from 'zod';
8
+ /**
9
+ * Metadata for a single agent in the canonical registry.
10
+ * Schema validation, CLI help, docs tables, and error messages all derive from this.
11
+ */
12
+ export interface AgentRegistryEntry {
13
+ readonly id: string;
14
+ readonly name: string;
15
+ readonly description: string;
16
+ readonly requiresExternalTool: boolean;
17
+ readonly requiresApiKey: boolean;
18
+ readonly builtIn: boolean;
19
+ readonly compatibleProviders: readonly string[] | 'all';
20
+ }
21
+ /**
22
+ * Canonical agent registry — the sole authority for agent identity.
23
+ * Adding a new agent without updating this registry is impossible because
24
+ * AgentSchema is derived from it.
25
+ */
26
+ export declare const AGENT_REGISTRY: readonly AgentRegistryEntry[];
27
+ /**
28
+ * Look up an agent by ID from the registry.
29
+ */
30
+ export declare function getAgentById(id: string): AgentRegistryEntry | undefined;
31
+ /**
32
+ * Get agents compatible with a given provider.
33
+ * Returns all agents if provider is null/undefined.
34
+ */
35
+ export declare function getCompatibleAgents(provider: string | null | undefined): AgentRegistryEntry[];
8
36
  export declare const AgentSchema: z.ZodEnum<{
9
- control_flow: "control_flow";
10
- semgrep: "semgrep";
11
- reviewdog: "reviewdog";
12
- opencode: "opencode";
13
- pr_agent: "pr_agent";
14
- local_llm: "local_llm";
15
- ai_semantic_review: "ai_semantic_review";
37
+ [x: string]: string;
16
38
  }>;
17
39
  export declare const PassSchema: z.ZodObject<{
18
40
  name: z.ZodString;
19
41
  agents: z.ZodArray<z.ZodEnum<{
20
- control_flow: "control_flow";
21
- semgrep: "semgrep";
22
- reviewdog: "reviewdog";
23
- opencode: "opencode";
24
- pr_agent: "pr_agent";
25
- local_llm: "local_llm";
26
- ai_semantic_review: "ai_semantic_review";
42
+ [x: string]: string;
27
43
  }>>;
28
44
  enabled: z.ZodDefault<z.ZodBoolean>;
29
45
  required: z.ZodDefault<z.ZodBoolean>;
@@ -120,6 +136,50 @@ export declare const ProviderSchema: z.ZodEnum<{
120
136
  "azure-openai": "azure-openai";
121
137
  ollama: "ollama";
122
138
  }>;
139
+ export declare const SuppressionRuleSchema: z.ZodObject<{
140
+ rule: z.ZodOptional<z.ZodString>;
141
+ message: z.ZodOptional<z.ZodString>;
142
+ file: z.ZodOptional<z.ZodString>;
143
+ severity: z.ZodOptional<z.ZodEnum<{
144
+ error: "error";
145
+ warning: "warning";
146
+ info: "info";
147
+ }>>;
148
+ reason: z.ZodString;
149
+ breadth_override: z.ZodOptional<z.ZodBoolean>;
150
+ breadth_override_reason: z.ZodOptional<z.ZodString>;
151
+ approved_by: z.ZodOptional<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ export declare const SuppressionsSchema: z.ZodObject<{
154
+ rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
155
+ rule: z.ZodOptional<z.ZodString>;
156
+ message: z.ZodOptional<z.ZodString>;
157
+ file: z.ZodOptional<z.ZodString>;
158
+ severity: z.ZodOptional<z.ZodEnum<{
159
+ error: "error";
160
+ warning: "warning";
161
+ info: "info";
162
+ }>>;
163
+ reason: z.ZodString;
164
+ breadth_override: z.ZodOptional<z.ZodBoolean>;
165
+ breadth_override_reason: z.ZodOptional<z.ZodString>;
166
+ approved_by: z.ZodOptional<z.ZodString>;
167
+ }, z.core.$strip>>>;
168
+ disable_matchers: z.ZodDefault<z.ZodArray<z.ZodEnum<{
169
+ "express-error-mw": "express-error-mw";
170
+ "ts-unused-prefix": "ts-unused-prefix";
171
+ "exhaustive-switch": "exhaustive-switch";
172
+ "react-query-dedup": "react-query-dedup";
173
+ "promise-allsettled-order": "promise-allsettled-order";
174
+ "safe-local-file-read": "safe-local-file-read";
175
+ "exhaustive-type-narrowed-switch": "exhaustive-type-narrowed-switch";
176
+ "error-object-xss": "error-object-xss";
177
+ "thin-wrapper-stdlib": "thin-wrapper-stdlib";
178
+ }>>>;
179
+ security_override_allowlist: z.ZodDefault<z.ZodArray<z.ZodString>>;
180
+ }, z.core.$strip>;
181
+ export type SuppressionRule = z.infer<typeof SuppressionRuleSchema>;
182
+ export type Suppressions = z.infer<typeof SuppressionsSchema>;
123
183
  export declare const ConfigSchema: z.ZodObject<{
124
184
  version: z.ZodDefault<z.ZodNumber>;
125
185
  trusted_only: z.ZodDefault<z.ZodBoolean>;
@@ -133,13 +193,7 @@ export declare const ConfigSchema: z.ZodObject<{
133
193
  passes: z.ZodDefault<z.ZodArray<z.ZodObject<{
134
194
  name: z.ZodString;
135
195
  agents: z.ZodArray<z.ZodEnum<{
136
- control_flow: "control_flow";
137
- semgrep: "semgrep";
138
- reviewdog: "reviewdog";
139
- opencode: "opencode";
140
- pr_agent: "pr_agent";
141
- local_llm: "local_llm";
142
- ai_semantic_review: "ai_semantic_review";
196
+ [x: string]: string;
143
197
  }>>;
144
198
  enabled: z.ZodDefault<z.ZodBoolean>;
145
199
  required: z.ZodDefault<z.ZodBoolean>;
@@ -272,6 +326,34 @@ export declare const ConfigSchema: z.ZodObject<{
272
326
  "azure-openai": "azure-openai";
273
327
  ollama: "ollama";
274
328
  }>>;
329
+ suppressions: z.ZodOptional<z.ZodObject<{
330
+ rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
331
+ rule: z.ZodOptional<z.ZodString>;
332
+ message: z.ZodOptional<z.ZodString>;
333
+ file: z.ZodOptional<z.ZodString>;
334
+ severity: z.ZodOptional<z.ZodEnum<{
335
+ error: "error";
336
+ warning: "warning";
337
+ info: "info";
338
+ }>>;
339
+ reason: z.ZodString;
340
+ breadth_override: z.ZodOptional<z.ZodBoolean>;
341
+ breadth_override_reason: z.ZodOptional<z.ZodString>;
342
+ approved_by: z.ZodOptional<z.ZodString>;
343
+ }, z.core.$strip>>>;
344
+ disable_matchers: z.ZodDefault<z.ZodArray<z.ZodEnum<{
345
+ "express-error-mw": "express-error-mw";
346
+ "ts-unused-prefix": "ts-unused-prefix";
347
+ "exhaustive-switch": "exhaustive-switch";
348
+ "react-query-dedup": "react-query-dedup";
349
+ "promise-allsettled-order": "promise-allsettled-order";
350
+ "safe-local-file-read": "safe-local-file-read";
351
+ "exhaustive-type-narrowed-switch": "exhaustive-type-narrowed-switch";
352
+ "error-object-xss": "error-object-xss";
353
+ "thin-wrapper-stdlib": "thin-wrapper-stdlib";
354
+ }>>>;
355
+ security_override_allowlist: z.ZodDefault<z.ZodArray<z.ZodString>>;
356
+ }, z.core.$strip>>;
275
357
  }, z.core.$strip>;
276
358
  export type Config = z.infer<typeof ConfigSchema>;
277
359
  export type Pass = z.infer<typeof PassSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/config/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,WAAW;;;;;;;;EAQtB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;iBAUrB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;iBAavB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;iBAMhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;iBAM7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;iBAG1B,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;iBAKvB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;iBAGzB,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY;;iBAGvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;EAA4D,CAAC;AAExF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BvB,CAAC;AAGH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/config/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;CACzD;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,kBAAkB,EAgE9C,CAAC;AAKX;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEvE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,EAAE,CAK7F;AAGD,eAAO,MAAM,WAAW;;EAAoB,CAAC;AAE7C,eAAO,MAAM,UAAU;;;;;;;iBAUrB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;iBAavB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;iBAMhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;iBAM7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;iBAG1B,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;iBAKvB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;iBAGzB,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY;;iBAGvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;EAA4D,CAAC;AAmBxF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAuE/B,CAAC;AAEJ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAU3B,CAAC;AAEL,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCvB,CAAC;AAGH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -6,16 +6,95 @@
6
6
  */
7
7
  import { z } from 'zod';
8
8
  import { ControlFlowConfigSchema } from '../agents/control_flow/types.js';
9
- // Schema definitions
10
- export const AgentSchema = z.enum([
11
- 'semgrep',
12
- 'reviewdog',
13
- 'opencode',
14
- 'pr_agent',
15
- 'local_llm',
16
- 'ai_semantic_review',
17
- 'control_flow',
18
- ]);
9
+ /**
10
+ * Canonical agent registry — the sole authority for agent identity.
11
+ * Adding a new agent without updating this registry is impossible because
12
+ * AgentSchema is derived from it.
13
+ */
14
+ export const AGENT_REGISTRY = [
15
+ {
16
+ id: 'semgrep',
17
+ name: 'Semgrep',
18
+ description: 'Static analysis via Semgrep CLI',
19
+ requiresExternalTool: true,
20
+ requiresApiKey: false,
21
+ builtIn: false,
22
+ compatibleProviders: 'all',
23
+ },
24
+ {
25
+ id: 'reviewdog',
26
+ name: 'Reviewdog',
27
+ description: 'Lint aggregation via Reviewdog CLI',
28
+ requiresExternalTool: true,
29
+ requiresApiKey: false,
30
+ builtIn: false,
31
+ compatibleProviders: 'all',
32
+ },
33
+ {
34
+ id: 'opencode',
35
+ name: 'OpenCode',
36
+ description: 'AI code review via cloud LLM',
37
+ requiresExternalTool: false,
38
+ requiresApiKey: true,
39
+ builtIn: false,
40
+ compatibleProviders: ['anthropic', 'openai', 'azure-openai'],
41
+ },
42
+ {
43
+ id: 'pr_agent',
44
+ name: 'PR Agent',
45
+ description: 'AI pull request analysis via cloud LLM',
46
+ requiresExternalTool: false,
47
+ requiresApiKey: true,
48
+ builtIn: false,
49
+ compatibleProviders: ['anthropic', 'openai', 'azure-openai'],
50
+ },
51
+ {
52
+ id: 'local_llm',
53
+ name: 'Local LLM',
54
+ description: 'AI code review via local Ollama model',
55
+ requiresExternalTool: false,
56
+ requiresApiKey: false,
57
+ builtIn: false,
58
+ compatibleProviders: ['ollama'],
59
+ },
60
+ {
61
+ id: 'ai_semantic_review',
62
+ name: 'AI Semantic Review',
63
+ description: 'Semantic analysis via cloud LLM',
64
+ requiresExternalTool: false,
65
+ requiresApiKey: true,
66
+ builtIn: false,
67
+ compatibleProviders: ['anthropic', 'openai', 'azure-openai'],
68
+ },
69
+ {
70
+ id: 'control_flow',
71
+ name: 'Control Flow',
72
+ description: 'Built-in TypeScript control flow analysis',
73
+ requiresExternalTool: false,
74
+ requiresApiKey: false,
75
+ builtIn: true,
76
+ compatibleProviders: 'all',
77
+ },
78
+ ];
79
+ /** All valid agent IDs, derived from the registry */
80
+ const AGENT_IDS = AGENT_REGISTRY.map((a) => a.id);
81
+ /**
82
+ * Look up an agent by ID from the registry.
83
+ */
84
+ export function getAgentById(id) {
85
+ return AGENT_REGISTRY.find((a) => a.id === id);
86
+ }
87
+ /**
88
+ * Get agents compatible with a given provider.
89
+ * Returns all agents if provider is null/undefined.
90
+ */
91
+ export function getCompatibleAgents(provider) {
92
+ if (!provider)
93
+ return [...AGENT_REGISTRY];
94
+ return AGENT_REGISTRY.filter((a) => a.compatibleProviders === 'all' || a.compatibleProviders.includes(provider));
95
+ }
96
+ // Schema definitions — AgentSchema derived from registry
97
+ export const AgentSchema = z.enum(AGENT_IDS);
19
98
  export const PassSchema = z.object({
20
99
  name: z.string(),
21
100
  agents: z.array(AgentSchema),
@@ -87,6 +166,92 @@ export const ModelsSchema = z.object({
87
166
  * Required when multiple provider keys are present with MODEL set.
88
167
  */
89
168
  export const ProviderSchema = z.enum(['anthropic', 'openai', 'azure-openai', 'ollama']);
169
+ // =============================================================================
170
+ // Suppression Schema (FR-022)
171
+ // =============================================================================
172
+ /** Valid matcher IDs for disable_matchers */
173
+ const VALID_MATCHER_IDS = [
174
+ 'express-error-mw',
175
+ 'ts-unused-prefix',
176
+ 'exhaustive-switch',
177
+ 'react-query-dedup',
178
+ 'promise-allsettled-order',
179
+ 'safe-local-file-read',
180
+ 'exhaustive-type-narrowed-switch',
181
+ 'error-object-xss',
182
+ 'thin-wrapper-stdlib',
183
+ ];
184
+ export const SuppressionRuleSchema = z
185
+ .object({
186
+ /** Glob pattern against finding.ruleId */
187
+ rule: z.string().optional(),
188
+ /** Anchored regex pattern against finding.message */
189
+ message: z.string().optional(),
190
+ /** Glob pattern against finding.file */
191
+ file: z.string().optional(),
192
+ /** Exact match against finding.severity */
193
+ severity: z.enum(['error', 'warning', 'info']).optional(),
194
+ /** Mandatory audit reason */
195
+ reason: z.string().min(1),
196
+ /** Allow broad suppression (>20 matches in CI) */
197
+ breadth_override: z.boolean().optional(),
198
+ /** Justification for breadth override */
199
+ breadth_override_reason: z.string().optional(),
200
+ /** Person or team who approved the override */
201
+ approved_by: z.string().optional(),
202
+ })
203
+ .refine((rule) => rule.rule !== undefined || rule.message !== undefined || rule.file !== undefined, { message: 'Suppression rule must specify at least one of: rule, message, file' })
204
+ .refine((rule) => {
205
+ if (rule.message === undefined)
206
+ return true;
207
+ if (rule.message.length === 0)
208
+ return false;
209
+ // FR-022: Message patterns MUST be fully anchored (^ and $).
210
+ if (!(rule.message.startsWith('^') && rule.message.endsWith('$')))
211
+ return false;
212
+ // Reject blanket patterns that match everything (^.*$, ^.+$, ^.{0,}$)
213
+ const blanketPatterns = ['^.*$', '^.+$', '^.{0,}$'];
214
+ if (blanketPatterns.includes(rule.message))
215
+ return false;
216
+ return true;
217
+ }, {
218
+ message: "Message pattern must be fully anchored (^...$) and not a blanket match. Use '^specific pattern$' for exact match or '^.*specific.*$' for substring.",
219
+ })
220
+ .refine((rule) => {
221
+ if (rule.rule === undefined)
222
+ return true;
223
+ // Reject blanket rule globs that match all rule IDs
224
+ const blanketGlobs = ['*', '**', '**/*'];
225
+ return !blanketGlobs.includes(rule.rule);
226
+ }, { message: "Rule glob must be scoped (e.g., 'semantic/*'), not a blanket '*'." })
227
+ .refine((rule) => {
228
+ if (rule.file === undefined)
229
+ return true;
230
+ // Reject blanket file globs that match all files
231
+ const blanketGlobs = ['*', '**', '**/*', '**/**'];
232
+ return !blanketGlobs.includes(rule.file);
233
+ }, { message: "File glob must be scoped (e.g., 'tests/**'), not a blanket '**'." })
234
+ .refine((rule) => {
235
+ if (!rule.breadth_override)
236
+ return true;
237
+ return (rule.breadth_override_reason !== undefined &&
238
+ rule.breadth_override_reason.length > 0 &&
239
+ rule.approved_by !== undefined &&
240
+ rule.approved_by.length > 0);
241
+ }, {
242
+ message: 'breadth_override requires both breadth_override_reason and approved_by to be specified',
243
+ });
244
+ export const SuppressionsSchema = z
245
+ .object({
246
+ rules: z.array(SuppressionRuleSchema).default([]),
247
+ /** Matcher IDs to disable in the framework convention filter */
248
+ disable_matchers: z.array(z.enum(VALID_MATCHER_IDS)).default([]),
249
+ /** Rule reasons authorized to suppress error-severity findings with breadth override */
250
+ security_override_allowlist: z.array(z.string()).default([]),
251
+ })
252
+ .refine((s) => s.rules.length <= 50, {
253
+ message: 'Maximum 50 suppression rules allowed',
254
+ });
90
255
  export const ConfigSchema = z.object({
91
256
  version: z.number().default(1),
92
257
  trusted_only: z.boolean().default(true),
@@ -117,5 +282,7 @@ export const ConfigSchema = z.object({
117
282
  * REQUIRED when multiple provider keys are present AND MODEL is set (prevents ambiguity).
118
283
  */
119
284
  provider: ProviderSchema.optional(),
285
+ /** User-configurable finding suppressions (FR-022) */
286
+ suppressions: SuppressionsSchema.optional(),
120
287
  });
121
288
  //# sourceMappingURL=schemas.js.map