ownkey 1.1.2 → 1.3.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 (47) hide show
  1. package/README.md +43 -0
  2. package/dist/ai/factory.d.ts +3 -1
  3. package/dist/ai/factory.d.ts.map +1 -1
  4. package/dist/ai/factory.js +5 -5
  5. package/dist/ai/factory.js.map +1 -1
  6. package/dist/bench/cases.d.ts +25 -0
  7. package/dist/bench/cases.d.ts.map +1 -0
  8. package/dist/bench/cases.js +142 -0
  9. package/dist/bench/cases.js.map +1 -0
  10. package/dist/bench/score.d.ts +46 -0
  11. package/dist/bench/score.d.ts.map +1 -0
  12. package/dist/bench/score.js +61 -0
  13. package/dist/bench/score.js.map +1 -0
  14. package/dist/commands/bench.d.ts +9 -0
  15. package/dist/commands/bench.d.ts.map +1 -0
  16. package/dist/commands/bench.js +169 -0
  17. package/dist/commands/bench.js.map +1 -0
  18. package/dist/commands/review.d.ts.map +1 -1
  19. package/dist/commands/review.js +5 -24
  20. package/dist/commands/review.js.map +1 -1
  21. package/dist/core/review/run.d.ts +20 -0
  22. package/dist/core/review/run.d.ts.map +1 -0
  23. package/dist/core/review/run.js +32 -0
  24. package/dist/core/review/run.js.map +1 -0
  25. package/dist/index.js +17 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/ui/App.d.ts +17 -0
  28. package/dist/ui/App.d.ts.map +1 -0
  29. package/dist/ui/App.js +109 -0
  30. package/dist/ui/App.js.map +1 -0
  31. package/dist/ui/complete.d.ts +22 -0
  32. package/dist/ui/complete.d.ts.map +1 -0
  33. package/dist/ui/complete.js +41 -0
  34. package/dist/ui/complete.js.map +1 -0
  35. package/dist/ui/context.d.ts +11 -0
  36. package/dist/ui/context.d.ts.map +1 -0
  37. package/dist/ui/context.js +37 -0
  38. package/dist/ui/context.js.map +1 -0
  39. package/dist/ui/session.d.ts +3 -0
  40. package/dist/ui/session.d.ts.map +1 -0
  41. package/dist/ui/session.js +114 -0
  42. package/dist/ui/session.js.map +1 -0
  43. package/dist/ui/words.d.ts +6 -0
  44. package/dist/ui/words.d.ts.map +1 -0
  45. package/dist/ui/words.js +46 -0
  46. package/dist/ui/words.js.map +1 -0
  47. package/package.json +16 -12
package/README.md CHANGED
@@ -102,16 +102,33 @@ ownkey undo --list # recent applies
102
102
 
103
103
  For a whole-project audit of any folder, git or not, use `ownkey suggest .`
104
104
 
105
+ ## Interactive session
106
+
107
+ Run `ownkey` with no arguments in any project folder and it opens a session for that folder. The header shows the repository, branch, how many files changed, and which model is reviewing. Then type any command without the `ownkey` prefix:
108
+
109
+ ```text
110
+ › review --staged
111
+ › apply 3 --verify "npm test"
112
+ › undo --last
113
+ › bench --case off-by-one
114
+ ```
115
+
116
+ Tab completes commands and their flags, the arrow keys walk your history (kept in `~/.ownkey/history`), `help` lists every command, and Ctrl+D leaves. Commands run exactly as they do from the shell, including interactive prompts like the `config` wizard. Ctrl+C stops the running command without closing the session.
117
+
118
+ Outside an interactive terminal (pipes, scripts, CI), or with `OWNKEY_NO_UI=1` set, a bare `ownkey` prints help as before. Every subcommand behaves the same with or without the session.
119
+
105
120
  ## Commands
106
121
 
107
122
  | Command | Purpose |
108
123
  |---|---|
124
+ | `ownkey` | Open the interactive session for the current folder. |
109
125
  | `ownkey config` | Set the AI provider, model, endpoint, and optional database. Run with no flags for an interactive wizard. |
110
126
  | `ownkey review [path]` | Review git changes: staged, uncommitted, or a branch against its base. |
111
127
  | `ownkey suggest [path]` | Audit a whole folder and store suggestions. |
112
128
  | `ownkey scan [path]` | List the files OwnKey would analyze, honoring `.gitignore` and `.ownkeyignore`. |
113
129
  | `ownkey apply [id]` | Apply one suggestion, or all pending ones with `--all`, optionally verified. |
114
130
  | `ownkey undo [backup-id]` | Restore files from a backup created by `apply`. |
131
+ | `ownkey bench` | Measure review quality on seeded-bug cases with your model. |
115
132
 
116
133
  <details>
117
134
  <summary><strong>Full option reference</strong></summary>
@@ -147,6 +164,17 @@ For a whole-project audit of any folder, git or not, use `ownkey suggest .`
147
164
 
148
165
  Exit codes: `0` success, `1` the `--fail-on` gate tripped, `2` the review could not run.
149
166
 
167
+ #### `ownkey bench`
168
+
169
+ | Option | Description |
170
+ |---|---|
171
+ | `--list` | List the cases and exit. |
172
+ | `--case <ids>` | Comma-separated case ids to run. Default: all. |
173
+ | `--model <model>` | Use this model for the run instead of the configured one. |
174
+ | `--format <format>` | `text` (default), `json`, or `markdown`. |
175
+ | `--output <file>` | Write the report to a file. |
176
+ | `--min-recall <ratio>` | Exit with code 1 when recall is below this ratio, from `0` to `1`. |
177
+
150
178
  #### `ownkey scan [path]`
151
179
 
152
180
  | Option | Description |
@@ -190,6 +218,21 @@ Exit codes: `0` success, `1` the `--fail-on` gate tripped, `2` the review could
190
218
 
191
219
  </details>
192
220
 
221
+ ## Measuring review quality
222
+
223
+ `ownkey bench` shows how well a model reviews before you trust it with your pull requests. It runs a fixed set of small cases, each a throwaway git repository with one staged change:
224
+
225
+ - **Bug cases** plant one real defect: an off-by-one, SQL injection, a missing `await`, a null dereference, a hardcoded secret, path traversal, command injection, an inverted auth check, a mutable default argument, a numeric sort with the string comparator, a switch fallthrough, and `is` used to compare strings. They cover JavaScript, TypeScript, and Python.
226
+ - **Control cases** are correct changes. Anything reported there at medium severity or above is a false alarm.
227
+
228
+ ```bash
229
+ ownkey bench # all cases with your configured model
230
+ ownkey bench --model claude-opus-5-5 # compare another model on the same key
231
+ ownkey bench --format markdown --output bench.md
232
+ ```
233
+
234
+ It reports **recall** (planted bugs reported within 3 lines of the defect with a matching type), **precision** (matching findings over all findings at medium or above), and how many controls stayed clean. Low and info findings never count against a model. Bench runs in temporary directories and stores nothing, but it does send every case to your provider, so a full run costs about 15 small review calls.
235
+
193
236
  ## Providers
194
237
 
195
238
  | Provider | Setup | Default model |
@@ -6,7 +6,9 @@ export declare class AIProviderFactory {
6
6
  /**
7
7
  * Create an AI provider instance based on configuration
8
8
  */
9
- static createProvider(): Promise<AIProvider>;
9
+ static createProvider(overrides?: {
10
+ model?: string;
11
+ }): Promise<AIProvider>;
10
12
  /**
11
13
  * Test provider connection
12
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/ai/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAU3C;;GAEG;AACH,qBAAa,iBAAiB;IAC1B;;OAEG;WACU,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC;IA6ClD;;OAEG;WACU,YAAY,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBjE;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAepD;;OAEG;WACU,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAmBvF"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/ai/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAU3C;;GAEG;AACH,qBAAa,iBAAiB;IAC1B;;OAEG;WACU,cAAc,CAAC,SAAS,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA6CpF;;OAEG;WACU,YAAY,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBjE;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAepD;;OAEG;WACU,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAmBvF"}
@@ -13,7 +13,7 @@ export class AIProviderFactory {
13
13
  /**
14
14
  * Create an AI provider instance based on configuration
15
15
  */
16
- static async createProvider() {
16
+ static async createProvider(overrides = {}) {
17
17
  const config = await configManager.load();
18
18
  if (!config.activeProvider) {
19
19
  throw new ConfigError('No active AI provider configured. Run: ownkey config');
@@ -26,23 +26,23 @@ export class AIProviderFactory {
26
26
  if (!apiKey) {
27
27
  throw new ConfigError('Gemini API key not found. Run: ownkey config');
28
28
  }
29
- const geminiModel = config.providers.gemini?.model || GeminiProvider.getRecommendedModel();
29
+ const geminiModel = overrides.model || config.providers.gemini?.model || GeminiProvider.getRecommendedModel();
30
30
  return new GeminiProvider(apiKey, geminiModel);
31
31
  case 'openai':
32
32
  if (!apiKey) {
33
33
  throw new ConfigError('OpenAI API key not found. Run: ownkey config');
34
34
  }
35
- const openaiModel = config.providers.openai?.model || DEFAULT_MODELS.openai;
35
+ const openaiModel = overrides.model || config.providers.openai?.model || DEFAULT_MODELS.openai;
36
36
  return new OpenAIProvider(apiKey, openaiModel, config.providers.openai?.endpoint);
37
37
  case 'anthropic':
38
38
  if (!apiKey) {
39
39
  throw new ConfigError('Anthropic API key not found. Run: ownkey config');
40
40
  }
41
- const anthropicModel = config.providers.anthropic?.model || DEFAULT_MODELS.anthropic;
41
+ const anthropicModel = overrides.model || config.providers.anthropic?.model || DEFAULT_MODELS.anthropic;
42
42
  return new AnthropicProvider(apiKey, anthropicModel);
43
43
  case 'ollama':
44
44
  // Ollama doesn't require API key - runs locally
45
- const ollamaModel = config.providers.ollama?.model || DEFAULT_MODELS.ollama;
45
+ const ollamaModel = overrides.model || config.providers.ollama?.model || DEFAULT_MODELS.ollama;
46
46
  const ollamaBaseUrl = config.providers.ollama?.endpoint || 'http://localhost:11434';
47
47
  return new OllamaProvider(undefined, ollamaModel, ollamaBaseUrl);
48
48
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/ai/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAC1B;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc;QACvB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;QAE1C,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,WAAW,CAAC,sDAAsD,CAAC,CAAC;QAClF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAEvD,MAAM,CAAC,OAAO,CAAC,YAAY,QAAQ,cAAc,CAAC,CAAC;QAEnD,QAAQ,QAAQ,EAAE,CAAC;YACf,KAAK,QAAQ;gBACT,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,MAAM,IAAI,WAAW,CAAC,8CAA8C,CAAC,CAAC;gBAC1E,CAAC;gBACD,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,cAAc,CAAC,mBAAmB,EAAE,CAAC;gBAC3F,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAEnD,KAAK,QAAQ;gBACT,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,MAAM,IAAI,WAAW,CAAC,8CAA8C,CAAC,CAAC;gBAC1E,CAAC;gBACD,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC;gBAC5E,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAEtF,KAAK,WAAW;gBACZ,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,MAAM,IAAI,WAAW,CAAC,iDAAiD,CAAC,CAAC;gBAC7E,CAAC;gBACD,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,IAAI,cAAc,CAAC,SAAS,CAAC;gBACrF,OAAO,IAAI,iBAAiB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YAEzD,KAAK,QAAQ;gBACT,gDAAgD;gBAChD,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC;gBAC5E,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,IAAI,wBAAwB,CAAC;gBACpF,OAAO,IAAI,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;YAErE;gBACI,MAAM,IAAI,WAAW,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,QAAoB;QAC1C,IAAI,CAAC;YACD,MAAM,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACT,MAAM,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACJ,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC/C,CAAC;YACD,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAc,CAAC,CAAC;YACpD,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAgB;QACvC,QAAQ,QAAQ,EAAE,CAAC;YACf,KAAK,QAAQ;gBACT,OAAO,cAAc,CAAC,mBAAmB,EAAE,CAAC;YAChD,KAAK,QAAQ;gBACT,OAAO,cAAc,CAAC,MAAM,CAAC;YACjC,KAAK,WAAW;gBACZ,OAAO,cAAc,CAAC,SAAS,CAAC;YACpC,KAAK,QAAQ;gBACT,OAAO,cAAc,CAAC,MAAM,CAAC;YACjC;gBACI,OAAO,EAAE,CAAC;QAClB,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,QAAgB,EAAE,MAAc;QAC5D,IAAI,CAAC;YACD,QAAQ,QAAQ,EAAE,CAAC;gBACf,KAAK,QAAQ;oBACT,OAAO,MAAM,cAAc,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;gBAC3D,KAAK,QAAQ;oBACT,OAAO,MAAM,cAAc,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;gBAC3D,KAAK,WAAW;oBACZ,OAAO,MAAM,iBAAiB,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;gBAC9D,KAAK,QAAQ;oBACT,OAAO,MAAM,cAAc,CAAC,kBAAkB,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;gBACxE;oBACI,OAAO,EAAE,CAAC;YAClB,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,8BAA8B,QAAQ,GAAG,EAAE,KAAc,CAAC,CAAC;YACxE,OAAO,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;CACJ"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/ai/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { SuggestionType } from '../types/index.js';
2
+ /**
3
+ * Seeded-bug benchmark cases.
4
+ *
5
+ * Each case is a tiny repository: `base` is committed, then `changed` is staged and reviewed.
6
+ * A bug case plants exactly one defect in the change; `expect.needle` is a substring of the
7
+ * line that carries it. A control case is a correct change: anything the reviewer reports
8
+ * there at medium severity or above counts as a false alarm.
9
+ */
10
+ export interface BenchCase {
11
+ id: string;
12
+ title: string;
13
+ language: string;
14
+ file: string;
15
+ base: string;
16
+ changed: string;
17
+ /** Absent for control cases */
18
+ expect?: {
19
+ needle: string;
20
+ /** Finding types that count as a catch */
21
+ types: SuggestionType[];
22
+ };
23
+ }
24
+ export declare const BENCH_CASES: BenchCase[];
25
+ //# sourceMappingURL=cases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cases.d.ts","sourceRoot":"","sources":["../../src/bench/cases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,MAAM,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,0CAA0C;QAC1C,KAAK,EAAE,cAAc,EAAE,CAAC;KAC3B,CAAC;CACL;AAOD,eAAO,MAAM,WAAW,EAAE,SAAS,EA4VlC,CAAC"}
@@ -0,0 +1,142 @@
1
+ const lines = (...l) => l.join('\n') + '\n';
2
+ /** Fake, non-working payment key for the hardcoded-secret case. */
3
+ const FAKE_LIVE_KEY = ['sk', 'live', '51Hx9aQ2eZvKYlo2C8b7fW3mN0pR4tUv'].join('_');
4
+ export const BENCH_CASES = [
5
+ {
6
+ id: 'off-by-one',
7
+ title: 'Loop reads one past the end of the array',
8
+ language: 'javascript',
9
+ file: 'src/cart.js',
10
+ base: lines('export function subtotal(items) {', ' return 0;', '}'),
11
+ changed: lines('export function subtotal(items) {', ' let total = 0;', ' for (let i = 0; i <= items.length; i++) {', ' total += items[i].price * items[i].qty;', ' }', ' return total;', '}'),
12
+ expect: { needle: 'i <= items.length', types: ['bug'] },
13
+ },
14
+ {
15
+ id: 'sql-injection',
16
+ title: 'User input concatenated into a SQL query',
17
+ language: 'typescript',
18
+ file: 'src/users.ts',
19
+ base: lines("import { db } from './db';", '', 'export async function findUser(id: number) {', " return db.query('SELECT id, email FROM users WHERE id = $1', [id]);", '}'),
20
+ changed: lines("import { db } from './db';", '', 'export async function findUser(id: number) {', " return db.query('SELECT id, email FROM users WHERE id = $1', [id]);", '}', '', 'export async function searchUsers(name: string) {', " return db.query(`SELECT id, email FROM users WHERE name LIKE '%${name}%'`);", '}'),
21
+ expect: { needle: "LIKE '%${name}%'", types: ['security'] },
22
+ },
23
+ {
24
+ id: 'missing-await',
25
+ title: 'Async call not awaited inside try/catch',
26
+ language: 'typescript',
27
+ file: 'src/orders.ts',
28
+ base: lines("import { saveOrder } from './store';", '', 'export async function placeOrder(order: { id: string }) {', ' await saveOrder(order);', ' return { ok: true };', '}'),
29
+ changed: lines("import { saveOrder } from './store';", "import { log } from './log';", '', 'export async function placeOrder(order: { id: string }) {', ' try {', ' saveOrder(order);', ' return { ok: true };', ' } catch (err) {', " log.error('order failed', err);", ' return { ok: false };', ' }', '}'),
30
+ expect: { needle: ' saveOrder(order);', types: ['bug'] },
31
+ },
32
+ {
33
+ id: 'null-deref',
34
+ title: 'Optional lookup result used without a check',
35
+ language: 'typescript',
36
+ file: 'src/profile.ts',
37
+ base: lines('interface User { id: string; name: string; address?: { city: string } }', '', 'export function displayName(user: User): string {', ' return user.name;', '}'),
38
+ changed: lines('interface User { id: string; name: string; address?: { city: string } }', '', 'export function displayName(user: User): string {', ' return user.name;', '}', '', 'export function cityLabel(users: User[], id: string): string {', ' const user = users.find((u) => u.id === id);', ' return `${user.name} (${user.address.city})`;', '}'),
39
+ expect: { needle: '${user.name} (${user.address.city})', types: ['bug'] },
40
+ },
41
+ {
42
+ id: 'hardcoded-secret',
43
+ title: 'Live API key committed in source',
44
+ language: 'python',
45
+ file: 'app/payments.py',
46
+ base: lines('import os', 'import requests', '', 'def charge(amount_cents, token):', " key = os.environ['PAYMENTS_KEY']", " return requests.post('https://api.payments.example/charge', auth=(key, ''), data={'amount': amount_cents, 'source': token})"),
47
+ changed: lines('import requests', '',
48
+ // Assembled at runtime so this fake key never appears whole in OwnKey's own source
49
+ // (secret scanners would flag it); the case file on disk gets the full value.
50
+ `PAYMENTS_KEY = '${FAKE_LIVE_KEY}'`, '', 'def charge(amount_cents, token):', " return requests.post('https://api.payments.example/charge', auth=(PAYMENTS_KEY, ''), data={'amount': amount_cents, 'source': token})"),
51
+ expect: { needle: "PAYMENTS_KEY = '", types: ['security'] },
52
+ },
53
+ {
54
+ id: 'path-traversal',
55
+ title: 'Request parameter joined into a file path',
56
+ language: 'javascript',
57
+ file: 'src/files.js',
58
+ base: lines("import express from 'express';", '', 'export const router = express.Router();'),
59
+ changed: lines("import express from 'express';", "import path from 'path';", '', 'export const router = express.Router();', "const UPLOADS = path.resolve('uploads');", '', "router.get('/download', (req, res) => {", ' const file = path.join(UPLOADS, req.query.name);', ' res.sendFile(file);', '});'),
60
+ expect: { needle: 'path.join(UPLOADS, req.query.name)', types: ['security'] },
61
+ },
62
+ {
63
+ id: 'command-injection',
64
+ title: 'Shell command built from user input',
65
+ language: 'javascript',
66
+ file: 'src/thumbs.js',
67
+ base: lines("import { execFile } from 'child_process';", '', 'export function thumbnail(src, out, cb) {', " execFile('convert', [src, '-resize', '200x200', out], cb);", '}'),
68
+ changed: lines("import { exec } from 'child_process';", '', 'export function thumbnail(src, out, size, cb) {', ' exec(`convert ${src} -resize ${size} ${out}`, cb);', '}'),
69
+ expect: { needle: 'exec(`convert ${src}', types: ['security'] },
70
+ },
71
+ {
72
+ id: 'inverted-auth',
73
+ title: 'Authorization check inverted',
74
+ language: 'typescript',
75
+ file: 'src/admin.ts',
76
+ base: lines("import type { Request, Response, NextFunction } from 'express';", '', 'export function requireAdmin(req: Request, res: Response, next: NextFunction) {', " res.status(403).end();", '}'),
77
+ changed: lines("import type { Request, Response, NextFunction } from 'express';", '', 'export function requireAdmin(req: Request, res: Response, next: NextFunction) {', ' const user = (req as any).user;', ' if (!user || !user.isAdmin) {', ' return next();', ' }', " res.status(403).json({ error: 'forbidden' });", '}'),
78
+ expect: { needle: 'if (!user || !user.isAdmin)', types: ['security', 'bug'] },
79
+ },
80
+ {
81
+ id: 'mutable-default',
82
+ title: 'Mutable default argument shared across calls',
83
+ language: 'python',
84
+ file: 'app/tags.py',
85
+ base: lines('def normalize(tag):', ' return tag.strip().lower()'),
86
+ changed: lines('def normalize(tag):', ' return tag.strip().lower()', '', 'def add_tag(tag, tags=[]):', ' tags.append(normalize(tag))', ' return tags'),
87
+ expect: { needle: 'def add_tag(tag, tags=[])', types: ['bug'] },
88
+ },
89
+ {
90
+ id: 'numeric-sort',
91
+ title: 'Numbers sorted with the default string comparator',
92
+ language: 'javascript',
93
+ file: 'src/stats.js',
94
+ base: lines('export function mean(values) {', ' return values.reduce((a, b) => a + b, 0) / values.length;', '}'),
95
+ changed: lines('export function mean(values) {', ' return values.reduce((a, b) => a + b, 0) / values.length;', '}', '', 'export function median(values) {', ' const sorted = [...values].sort();', ' const mid = Math.floor(sorted.length / 2);', ' return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;', '}'),
96
+ expect: { needle: '[...values].sort()', types: ['bug'] },
97
+ },
98
+ {
99
+ id: 'switch-fallthrough',
100
+ title: 'Missing break makes one case fall into the next',
101
+ language: 'javascript',
102
+ file: 'src/pricing.js',
103
+ base: lines('export function discount(tier) {', ' return 0;', '}'),
104
+ changed: lines('export function discount(tier) {', ' let pct = 0;', ' switch (tier) {', " case 'gold':", ' pct = 20;', " case 'silver':", ' pct = 10;', ' break;', ' default:', ' pct = 0;', ' }', ' return pct;', '}'),
105
+ expect: { needle: 'pct = 20;', types: ['bug'] },
106
+ },
107
+ {
108
+ id: 'string-identity',
109
+ title: 'String compared with `is` instead of `==`',
110
+ language: 'python',
111
+ file: 'app/roles.py',
112
+ base: lines('def can_edit(user):', ' return False'),
113
+ changed: lines('def can_edit(user):', " role = user.get('role', '').lower()", " return role is 'editor' or role is 'admin'"),
114
+ expect: { needle: "role is 'editor'", types: ['bug'] },
115
+ },
116
+ // Controls: correct changes. Reports here at medium or above are false alarms.
117
+ {
118
+ id: 'clean-refactor',
119
+ title: 'Control: loop rewritten as reduce',
120
+ language: 'javascript',
121
+ file: 'src/cart.js',
122
+ base: lines('export function subtotal(items) {', ' let total = 0;', ' for (let i = 0; i < items.length; i++) {', ' total += items[i].price * items[i].qty;', ' }', ' return total;', '}'),
123
+ changed: lines('export function subtotal(items) {', ' return items.reduce((total, item) => total + item.price * item.qty, 0);', '}'),
124
+ },
125
+ {
126
+ id: 'clean-feature',
127
+ title: 'Control: parameterized query added',
128
+ language: 'typescript',
129
+ file: 'src/users.ts',
130
+ base: lines("import { db } from './db';", '', 'export async function findUser(id: number) {', " return db.query('SELECT id, email FROM users WHERE id = $1', [id]);", '}'),
131
+ changed: lines("import { db } from './db';", '', 'export async function findUser(id: number) {', " return db.query('SELECT id, email FROM users WHERE id = $1', [id]);", '}', '', 'export async function findByEmail(email: string) {', " return db.query('SELECT id, email FROM users WHERE email = $1', [email.trim().toLowerCase()]);", '}'),
132
+ },
133
+ {
134
+ id: 'clean-guard',
135
+ title: 'Control: null check added',
136
+ language: 'typescript',
137
+ file: 'src/profile.ts',
138
+ base: lines('interface User { id: string; name: string }', '', 'export function greet(user: User): string {', ' return `Hello, ${user.name}`;', '}'),
139
+ changed: lines('interface User { id: string; name: string }', '', 'export function greet(user: User | undefined): string {', " if (!user) return 'Hello, guest';", ' return `Hello, ${user.name}`;', '}'),
140
+ },
141
+ ];
142
+ //# sourceMappingURL=cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cases.js","sourceRoot":"","sources":["../../src/bench/cases.ts"],"names":[],"mappings":"AAyBA,MAAM,KAAK,GAAG,CAAC,GAAG,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEtD,mEAAmE;AACnE,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,kCAAkC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEnF,MAAM,CAAC,MAAM,WAAW,GAAgB;IACpC;QACI,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,0CAA0C;QACjD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,KAAK,CACP,mCAAmC,EACnC,eAAe,EACf,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,mCAAmC,EACnC,oBAAoB,EACpB,+CAA+C,EAC/C,iDAAiD,EACjD,OAAO,EACP,mBAAmB,EACnB,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAC1D;IACD;QACI,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,0CAA0C;QACjD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,4BAA4B,EAC5B,EAAE,EACF,8CAA8C,EAC9C,yEAAyE,EACzE,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,4BAA4B,EAC5B,EAAE,EACF,8CAA8C,EAC9C,yEAAyE,EACzE,GAAG,EACH,EAAE,EACF,mDAAmD,EACnD,iFAAiF,EACjF,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE;KAC9D;IACD;QACI,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,yCAAyC;QAChD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,KAAK,CACP,sCAAsC,EACtC,EAAE,EACF,2DAA2D,EAC3D,6BAA6B,EAC7B,0BAA0B,EAC1B,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,sCAAsC,EACtC,8BAA8B,EAC9B,EAAE,EACF,2DAA2D,EAC3D,WAAW,EACX,2BAA2B,EAC3B,8BAA8B,EAC9B,qBAAqB,EACrB,yCAAyC,EACzC,+BAA+B,EAC/B,OAAO,EACP,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,2BAA2B,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAClE;IACD;QACI,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,6CAA6C;QACpD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,KAAK,CACP,yEAAyE,EACzE,EAAE,EACF,mDAAmD,EACnD,uBAAuB,EACvB,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,yEAAyE,EACzE,EAAE,EACF,mDAAmD,EACnD,uBAAuB,EACvB,GAAG,EACH,EAAE,EACF,gEAAgE,EAChE,kDAAkD,EAClD,mDAAmD,EACnD,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,qCAAqC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAC5E;IACD;QACI,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,kCAAkC;QACzC,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,KAAK,CACP,WAAW,EACX,iBAAiB,EACjB,EAAE,EACF,kCAAkC,EAClC,sCAAsC,EACtC,iIAAiI,CACpI;QACD,OAAO,EAAE,KAAK,CACV,iBAAiB,EACjB,EAAE;QACF,mFAAmF;QACnF,8EAA8E;QAC9E,mBAAmB,aAAa,GAAG,EACnC,EAAE,EACF,kCAAkC,EAClC,0IAA0I,CAC7I;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE;KAC9D;IACD;QACI,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,2CAA2C;QAClD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,gCAAgC,EAChC,EAAE,EACF,yCAAyC,CAC5C;QACD,OAAO,EAAE,KAAK,CACV,gCAAgC,EAChC,0BAA0B,EAC1B,EAAE,EACF,yCAAyC,EACzC,0CAA0C,EAC1C,EAAE,EACF,yCAAyC,EACzC,sDAAsD,EACtD,yBAAyB,EACzB,KAAK,CACR;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,oCAAoC,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE;KAChF;IACD;QACI,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,qCAAqC;QAC5C,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,KAAK,CACP,2CAA2C,EAC3C,EAAE,EACF,2CAA2C,EAC3C,gEAAgE,EAChE,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,uCAAuC,EACvC,EAAE,EACF,iDAAiD,EACjD,wDAAwD,EACxD,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE;KAClE;IACD;QACI,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,8BAA8B;QACrC,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,iEAAiE,EACjE,EAAE,EACF,iFAAiF,EACjF,4BAA4B,EAC5B,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,iEAAiE,EACjE,EAAE,EACF,iFAAiF,EACjF,qCAAqC,EACrC,mCAAmC,EACnC,wBAAwB,EACxB,OAAO,EACP,mDAAmD,EACnD,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,6BAA6B,EAAE,KAAK,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE;KAChF;IACD;QACI,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,8CAA8C;QACrD,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,KAAK,CACP,qBAAqB,EACrB,gCAAgC,CACnC;QACD,OAAO,EAAE,KAAK,CACV,qBAAqB,EACrB,gCAAgC,EAChC,EAAE,EACF,4BAA4B,EAC5B,iCAAiC,EACjC,iBAAiB,CACpB;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,2BAA2B,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAClE;IACD;QACI,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,mDAAmD;QAC1D,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,gCAAgC,EAChC,+DAA+D,EAC/D,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,gCAAgC,EAChC,+DAA+D,EAC/D,GAAG,EACH,EAAE,EACF,kCAAkC,EAClC,wCAAwC,EACxC,gDAAgD,EAChD,mFAAmF,EACnF,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAC3D;IACD;QACI,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,iDAAiD;QACxD,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,KAAK,CACP,kCAAkC,EAClC,eAAe,EACf,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,kCAAkC,EAClC,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,OAAO,EACP,iBAAiB,EACjB,GAAG,CACN;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KAClD;IACD;QACI,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,2CAA2C;QAClD,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,qBAAqB,EACrB,kBAAkB,CACrB;QACD,OAAO,EAAE,KAAK,CACV,qBAAqB,EACrB,yCAAyC,EACzC,gDAAgD,CACnD;QACD,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;KACzD;IACD,+EAA+E;IAC/E;QACI,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,mCAAmC;QAC1C,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,KAAK,CACP,mCAAmC,EACnC,oBAAoB,EACpB,8CAA8C,EAC9C,iDAAiD,EACjD,OAAO,EACP,mBAAmB,EACnB,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,mCAAmC,EACnC,6EAA6E,EAC7E,GAAG,CACN;KACJ;IACD;QACI,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,oCAAoC;QAC3C,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,KAAK,CACP,4BAA4B,EAC5B,EAAE,EACF,8CAA8C,EAC9C,yEAAyE,EACzE,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,4BAA4B,EAC5B,EAAE,EACF,8CAA8C,EAC9C,yEAAyE,EACzE,GAAG,EACH,EAAE,EACF,oDAAoD,EACpD,oGAAoG,EACpG,GAAG,CACN;KACJ;IACD;QACI,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,2BAA2B;QAClC,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,KAAK,CACP,6CAA6C,EAC7C,EAAE,EACF,6CAA6C,EAC7C,mCAAmC,EACnC,GAAG,CACN;QACD,OAAO,EAAE,KAAK,CACV,6CAA6C,EAC7C,EAAE,EACF,yDAAyD,EACzD,uCAAuC,EACvC,mCAAmC,EACnC,GAAG,CACN;KACJ;CACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { Severity } from '../types/index.js';
2
+ import type { BenchCase } from './cases.js';
3
+ /** A reported finding, reduced to what scoring needs. */
4
+ export interface BenchFinding {
5
+ filePath: string;
6
+ line: number;
7
+ type: string;
8
+ severity: Severity;
9
+ content: string;
10
+ }
11
+ export interface CaseResult {
12
+ id: string;
13
+ title: string;
14
+ kind: 'bug' | 'control';
15
+ /** Bug cases: the planted defect was reported */
16
+ caught: boolean;
17
+ /** Findings that matched the planted defect */
18
+ matched: number;
19
+ /** Unmatched findings at medium or above (false alarms on controls, noise on bug cases) */
20
+ noise: number;
21
+ findings: BenchFinding[];
22
+ error?: string;
23
+ durationMs: number;
24
+ }
25
+ export interface BenchSummary {
26
+ bugCases: number;
27
+ caught: number;
28
+ /** caught / bugCases */
29
+ recall: number;
30
+ /** matched findings / (matched + noise) over all cases; 1 when nothing was reported */
31
+ precision: number;
32
+ controlCases: number;
33
+ /** Control cases with at least one false alarm */
34
+ controlsFlagged: number;
35
+ errors: number;
36
+ }
37
+ /** How far (in lines) a finding may sit from the planted line and still count. */
38
+ export declare const LINE_TOLERANCE = 3;
39
+ /** Noise threshold: low/info nits never count against a reviewer. */
40
+ export declare const NOISE_SEVERITY: Severity;
41
+ /** 1-based line of the planted defect in the changed file. */
42
+ export declare function plantedLine(c: BenchCase): number;
43
+ export declare function matchesPlanted(c: BenchCase, f: BenchFinding): boolean;
44
+ export declare function scoreCase(c: BenchCase, findings: BenchFinding[], durationMs?: number, error?: string): CaseResult;
45
+ export declare function summarize(results: CaseResult[]): BenchSummary;
46
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/bench/score.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,yDAAyD;AACzD,MAAM,WAAW,YAAY;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACxB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,kFAAkF;AAClF,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,qEAAqE;AACrE,eAAO,MAAM,cAAc,EAAE,QAAmB,CAAC;AAEjD,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAKhD;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAKrE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,UAAU,SAAI,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAkB5G;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,YAAY,CAe7D"}
@@ -0,0 +1,61 @@
1
+ import { meetsSeverity } from '../utils/severity.js';
2
+ /** How far (in lines) a finding may sit from the planted line and still count. */
3
+ export const LINE_TOLERANCE = 3;
4
+ /** Noise threshold: low/info nits never count against a reviewer. */
5
+ export const NOISE_SEVERITY = 'medium';
6
+ /** 1-based line of the planted defect in the changed file. */
7
+ export function plantedLine(c) {
8
+ if (!c.expect)
9
+ return 0;
10
+ const idx = c.changed.split('\n').findIndex((l) => l.includes(c.expect.needle));
11
+ if (idx === -1)
12
+ throw new Error(`Bench case ${c.id}: needle not found in changed file`);
13
+ return idx + 1;
14
+ }
15
+ export function matchesPlanted(c, f) {
16
+ if (!c.expect)
17
+ return false;
18
+ if (f.filePath !== c.file)
19
+ return false;
20
+ if (Math.abs(f.line - plantedLine(c)) > LINE_TOLERANCE)
21
+ return false;
22
+ return c.expect.types.includes(f.type);
23
+ }
24
+ export function scoreCase(c, findings, durationMs = 0, error) {
25
+ let matched = 0;
26
+ let noise = 0;
27
+ for (const f of findings) {
28
+ if (matchesPlanted(c, f))
29
+ matched++;
30
+ else if (meetsSeverity(f.severity, NOISE_SEVERITY))
31
+ noise++;
32
+ }
33
+ return {
34
+ id: c.id,
35
+ title: c.title,
36
+ kind: c.expect ? 'bug' : 'control',
37
+ caught: matched > 0,
38
+ matched,
39
+ noise,
40
+ findings,
41
+ error,
42
+ durationMs,
43
+ };
44
+ }
45
+ export function summarize(results) {
46
+ const bugs = results.filter((r) => r.kind === 'bug');
47
+ const controls = results.filter((r) => r.kind === 'control');
48
+ const caught = bugs.filter((r) => r.caught).length;
49
+ const matched = results.reduce((n, r) => n + r.matched, 0);
50
+ const noise = results.reduce((n, r) => n + r.noise, 0);
51
+ return {
52
+ bugCases: bugs.length,
53
+ caught,
54
+ recall: bugs.length ? caught / bugs.length : 0,
55
+ precision: matched + noise ? matched / (matched + noise) : 1,
56
+ controlCases: controls.length,
57
+ controlsFlagged: controls.filter((r) => r.noise > 0).length,
58
+ errors: results.filter((r) => r.error).length,
59
+ };
60
+ }
61
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/bench/score.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ import type { AIProvider } from '../ai/provider.js';
3
+ import { type BenchCase } from '../bench/cases.js';
4
+ import { type BenchSummary, type CaseResult } from '../bench/score.js';
5
+ export declare const benchCommand: Command;
6
+ /** Build a throwaway repo for one case, stage the change, and review it. Nothing is persisted. */
7
+ export declare function runCase(c: BenchCase, provider: AIProvider): Promise<CaseResult>;
8
+ export declare function toMarkdown(results: CaseResult[], s: BenchSummary, model: string): string;
9
+ //# sourceMappingURL=bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.d.ts","sourceRoot":"","sources":["../../src/commands/bench.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAA2C,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKhH,eAAO,MAAM,YAAY,SAgEnB,CAAC;AAiBP,kGAAkG;AAClG,wBAAsB,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BrF;AA4BD,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAexF"}