@rigour-labs/core 6.6.5 → 6.7.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 (154) hide show
  1. package/README.md +16 -92
  2. package/dist/environment.test.js +0 -4
  3. package/dist/gates/agent-team.d.ts +0 -2
  4. package/dist/gates/agent-team.js +0 -8
  5. package/dist/gates/checkpoint.d.ts +0 -1
  6. package/dist/gates/checkpoint.js +0 -1
  7. package/dist/gates/context.d.ts +0 -4
  8. package/dist/gates/context.js +7 -17
  9. package/dist/gates/deep-analysis.d.ts +0 -1
  10. package/dist/gates/deep-runner.js +0 -1
  11. package/dist/gates/frontend-secret-exposure.d.ts +1 -1
  12. package/dist/gates/frontend-secret-exposure.js +9 -3
  13. package/dist/gates/frontend-secret-exposure.test.js +6 -3
  14. package/dist/gates/hallucinated-imports/index.d.ts +0 -2
  15. package/dist/gates/hallucinated-imports/index.js +2 -2
  16. package/dist/gates/hallucinated-imports/js-resolver.js +1 -0
  17. package/dist/gates/hallucinated-imports.test.js +10 -0
  18. package/dist/gates/runner.js +3 -4
  19. package/dist/gates/test-quality-matchers.d.ts +0 -4
  20. package/dist/gates/test-quality-matchers.js +0 -7
  21. package/dist/gates/test-quality.d.ts +0 -2
  22. package/dist/gates/test-quality.js +1 -10
  23. package/dist/hooks/checker.js +1 -1
  24. package/dist/hooks/index.d.ts +2 -3
  25. package/dist/hooks/index.js +1 -2
  26. package/dist/hooks/stop-review.d.ts +6 -9
  27. package/dist/hooks/stop-review.js +5 -10
  28. package/dist/hooks/types.d.ts +0 -14
  29. package/dist/hooks/types.js +1 -15
  30. package/dist/index.d.ts +11 -3
  31. package/dist/index.js +11 -3
  32. package/dist/inference/cloud-provider.d.ts +2 -0
  33. package/dist/inference/cloud-provider.js +16 -6
  34. package/dist/inference/cloud-provider.test.js +21 -0
  35. package/dist/pattern-index/embeddings.d.ts +0 -5
  36. package/dist/pattern-index/embeddings.js +11 -4
  37. package/dist/pattern-index/semantic-runtime.d.ts +10 -0
  38. package/dist/pattern-index/semantic-runtime.js +46 -0
  39. package/dist/pattern-index/semantic-runtime.test.d.ts +1 -0
  40. package/dist/pattern-index/semantic-runtime.test.js +31 -0
  41. package/dist/review/backtest-init.d.ts +8 -0
  42. package/dist/review/backtest-init.js +87 -0
  43. package/dist/review/backtest-init.test.d.ts +1 -0
  44. package/dist/review/backtest-init.test.js +74 -0
  45. package/dist/review/backtest-judges.d.ts +38 -0
  46. package/dist/review/backtest-judges.js +63 -0
  47. package/dist/review/backtest-judges.test.d.ts +1 -0
  48. package/dist/review/backtest-judges.test.js +32 -0
  49. package/dist/review/backtest.d.ts +320 -0
  50. package/dist/review/backtest.js +207 -0
  51. package/dist/review/backtest.test.d.ts +1 -0
  52. package/dist/review/backtest.test.js +129 -0
  53. package/dist/review/baseline.js +2 -1
  54. package/dist/review/dead-code.test.js +15 -0
  55. package/dist/review/fix-scope.d.ts +21 -0
  56. package/dist/review/fix-scope.js +94 -0
  57. package/dist/review/fix-scope.test.d.ts +1 -0
  58. package/dist/review/fix-scope.test.js +104 -0
  59. package/dist/review/lint-overlay.d.ts +21 -0
  60. package/dist/review/lint-overlay.js +89 -0
  61. package/dist/review/orphan-files.js +3 -2
  62. package/dist/review/partial-wiring.js +5 -3
  63. package/dist/review/query-patterns.js +3 -2
  64. package/dist/review/quiet.d.ts +6 -0
  65. package/dist/review/quiet.js +12 -2
  66. package/dist/review/quiet.test.js +11 -0
  67. package/dist/review/review.d.ts +6 -0
  68. package/dist/review/review.js +10 -2
  69. package/dist/review/reviewer/adapters.d.ts +51 -0
  70. package/dist/review/reviewer/adapters.js +181 -0
  71. package/dist/review/reviewer/adapters.test.d.ts +1 -0
  72. package/dist/review/reviewer/adapters.test.js +23 -0
  73. package/dist/review/reviewer/background.d.ts +41 -0
  74. package/dist/review/reviewer/background.js +153 -0
  75. package/dist/review/reviewer/background.test.d.ts +1 -0
  76. package/dist/review/reviewer/background.test.js +118 -0
  77. package/dist/review/reviewer/consensus.d.ts +36 -0
  78. package/dist/review/reviewer/consensus.js +144 -0
  79. package/dist/review/reviewer/context.d.ts +75 -0
  80. package/dist/review/reviewer/context.js +140 -0
  81. package/dist/review/reviewer/context.test.d.ts +1 -0
  82. package/dist/review/reviewer/context.test.js +18 -0
  83. package/dist/review/reviewer/exec.d.ts +17 -0
  84. package/dist/review/reviewer/exec.js +27 -0
  85. package/dist/review/reviewer/exec.test.d.ts +1 -0
  86. package/dist/review/reviewer/exec.test.js +23 -0
  87. package/dist/review/reviewer/inputs.d.ts +39 -0
  88. package/dist/review/reviewer/inputs.js +107 -0
  89. package/dist/review/reviewer/merge-impact.d.ts +2 -0
  90. package/dist/review/reviewer/merge-impact.js +64 -0
  91. package/dist/review/reviewer/panel.d.ts +67 -0
  92. package/dist/review/reviewer/panel.js +101 -0
  93. package/dist/review/reviewer/panel.test.d.ts +1 -0
  94. package/dist/review/reviewer/panel.test.js +110 -0
  95. package/dist/review/reviewer/prompt.d.ts +32 -0
  96. package/dist/review/reviewer/prompt.js +145 -0
  97. package/dist/review/reviewer/settings.d.ts +58 -0
  98. package/dist/review/reviewer/settings.js +152 -0
  99. package/dist/review/reviewer/settings.test.d.ts +1 -0
  100. package/dist/review/reviewer/settings.test.js +57 -0
  101. package/dist/review/reviewer/store.d.ts +49 -0
  102. package/dist/review/reviewer/store.js +99 -0
  103. package/dist/review/reviewer/usage.d.ts +2 -0
  104. package/dist/review/reviewer/usage.js +28 -0
  105. package/dist/review/reviewer/usage.test.d.ts +1 -0
  106. package/dist/review/reviewer/usage.test.js +14 -0
  107. package/dist/review/reviewer/verdict.d.ts +141 -0
  108. package/dist/review/reviewer/verdict.js +166 -0
  109. package/dist/review/reviewer.d.ts +84 -49
  110. package/dist/review/reviewer.js +364 -195
  111. package/dist/review/reviewer.test.js +316 -50
  112. package/dist/review/test-files.d.ts +5 -1
  113. package/dist/review/test-files.js +6 -2
  114. package/dist/review/toolchain.d.ts +5 -2
  115. package/dist/review/toolchain.js +89 -7
  116. package/dist/review/toolchain.test.js +45 -4
  117. package/dist/review/typed/program.d.ts +26 -0
  118. package/dist/review/typed/program.js +90 -0
  119. package/dist/review/typed/redundancy.d.ts +10 -0
  120. package/dist/review/typed/redundancy.js +378 -0
  121. package/dist/review/typed/redundancy.test.d.ts +1 -0
  122. package/dist/review/typed/redundancy.test.js +179 -0
  123. package/dist/review/typed/schema-nullability.d.ts +6 -0
  124. package/dist/review/typed/schema-nullability.js +218 -0
  125. package/dist/review/typed/schema-nullability.test.d.ts +1 -0
  126. package/dist/review/typed/schema-nullability.test.js +63 -0
  127. package/dist/review/unused-exports.js +10 -7
  128. package/dist/semantic/learn/types.d.ts +2 -2
  129. package/dist/services/adaptive-thresholds.d.ts +2 -52
  130. package/dist/services/adaptive-thresholds.js +2 -147
  131. package/dist/services/adaptive-thresholds.test.js +1 -81
  132. package/dist/settings.d.ts +14 -6
  133. package/dist/settings.js +0 -12
  134. package/dist/storage/team-diagnostics.js +1 -1
  135. package/dist/storage/team-schema.d.ts +1 -1
  136. package/dist/storage/team-schema.js +2 -0
  137. package/dist/storage/team-store.js +26 -3
  138. package/dist/storage/team-sync-scope.test.js +35 -2
  139. package/dist/telemetry/telemetry.d.ts +5 -0
  140. package/dist/telemetry/telemetry.js +11 -1
  141. package/dist/templates/paradigms.js +0 -8
  142. package/dist/templates/presets.d.ts +0 -1
  143. package/dist/templates/presets.js +0 -23
  144. package/dist/templates/universal-config.js +16 -46
  145. package/dist/types/index.d.ts +202 -574
  146. package/dist/types/index.js +64 -67
  147. package/dist/types/index.test.d.ts +1 -0
  148. package/dist/types/index.test.js +33 -0
  149. package/dist/utils/command-line.d.ts +8 -0
  150. package/dist/utils/command-line.js +8 -0
  151. package/dist/utils/command-line.test.d.ts +1 -0
  152. package/dist/utils/command-line.test.js +8 -0
  153. package/dist/utils/profile.js +1 -1
  154. package/package.json +6 -7
@@ -0,0 +1,141 @@
1
+ import type { Spend, Tokens } from './adapters.js';
2
+ import type { PanelItem } from './panel.js';
3
+ export interface PriorPoint {
4
+ point: string;
5
+ review?: string;
6
+ severity?: 'blocking' | 'should-fix' | 'non-blocking';
7
+ resolved: boolean;
8
+ evidence?: string;
9
+ checked_siblings?: string[];
10
+ reviewer?: string;
11
+ }
12
+ interface Redundant {
13
+ file: string;
14
+ line?: number;
15
+ what: string;
16
+ made_redundant_by?: string;
17
+ removed: boolean;
18
+ reviewer?: string;
19
+ }
20
+ interface Read {
21
+ file: string;
22
+ line?: number;
23
+ read: string;
24
+ rules?: Array<{
25
+ rule: string;
26
+ known_before_read: boolean;
27
+ applied_before_read: boolean;
28
+ }>;
29
+ narrower_source?: string | null;
30
+ keys?: Array<{
31
+ name: string;
32
+ inputs: string;
33
+ stable_under_edit: boolean;
34
+ }>;
35
+ window_bounded?: boolean | null;
36
+ keyset?: boolean | null;
37
+ reviewer?: string;
38
+ }
39
+ interface Scan {
40
+ file: string;
41
+ line?: number;
42
+ function: string;
43
+ outer: string;
44
+ inner: string;
45
+ fix: string;
46
+ reviewer?: string;
47
+ }
48
+ interface MergeImpactItem {
49
+ symbol: string;
50
+ main_file: string;
51
+ call_site: string;
52
+ holds: boolean;
53
+ why: string;
54
+ reviewer?: string;
55
+ }
56
+ export interface Finding {
57
+ class: string;
58
+ file: string;
59
+ line?: number;
60
+ issue: string;
61
+ why?: string;
62
+ consequence?: string;
63
+ reviewer?: string;
64
+ }
65
+ export interface Verdict {
66
+ prior_points: PriorPoint[];
67
+ redundant: Redundant[];
68
+ reads: Read[];
69
+ scans: Scan[];
70
+ merge_impact: MergeImpactItem[];
71
+ findings: Finding[];
72
+ carried: string[];
73
+ resolved_previous: Array<{
74
+ id: string;
75
+ evidence: string;
76
+ }>;
77
+ reviewer?: string;
78
+ cost_usd?: number;
79
+ tokens?: Tokens;
80
+ reviewers?: Array<{
81
+ reviewer: string;
82
+ cost_usd?: number;
83
+ tokens?: Tokens;
84
+ }>;
85
+ /** With a panel: every judge's own item ids, and the panel's decision on each finding (reviewer/panel.ts). */
86
+ panel?: {
87
+ judgeItemIds: string[];
88
+ items: PanelItem[];
89
+ };
90
+ }
91
+ export interface OpenItem {
92
+ id: string;
93
+ kind: 'prior' | 'redundant' | 'read' | 'scan' | 'merge' | 'finding';
94
+ class: string;
95
+ file?: string;
96
+ line?: number;
97
+ issue: string;
98
+ evidence?: string;
99
+ /** For a finding: the wrong outcome or the cost. A finding without one is a note, never a block. */
100
+ consequence?: string;
101
+ reviewer?: string;
102
+ /** In delta mode, how the item reached this verdict. */
103
+ status?: 'carried' | 'not accounted for';
104
+ }
105
+ /** Checks a file (and a line) against the checkout; an item that fails cannot block. */
106
+ export type Verify = (file: string, line: number | undefined) => boolean;
107
+ /** The verdict in a reviewer's answer, or why it is not one. `needsPriorPoints`: a human review exists and none of its points is carried. */
108
+ export declare function parseVerdict(text: string, needsPriorPoints: boolean, reviewer: string, spend: Spend): {
109
+ verdict: Verdict;
110
+ } | {
111
+ error: string;
112
+ };
113
+ /** Several reviewers as one: a prior point is resolved only when every reviewer says so; every other item stands, tagged with who found it. */
114
+ export declare function mergeVerdicts(parts: Verdict[]): Verdict;
115
+ /**
116
+ * Delta mode: a human point the previous verdict resolved stays resolved unless the new commits
117
+ * touch a file its evidence names; it is carried into the new verdict so nothing disappears, and
118
+ * the reviewer is not paid to judge it again.
119
+ */
120
+ export declare function carryResolved(verdict: Verdict, previous: Verdict | undefined, touched: Set<string>): Verdict;
121
+ /** True when the evidence names a file in the set (`src/a.ts:12`, `src/a.ts`). */
122
+ export declare function evidenceTouched(evidence: string | undefined, touched: Set<string>): boolean;
123
+ export interface Accounting {
124
+ /** What blocks: everything the reviewers reported that checks out, plus previous items still open. */
125
+ open: OpenItem[];
126
+ /** Items naming code the checkout does not have: shown, never a block. */
127
+ unverified: OpenItem[];
128
+ /** Previous open items the reviewer resolved, with the evidence. */
129
+ resolved: Array<{
130
+ item: OpenItem;
131
+ evidence: string;
132
+ }>;
133
+ /** Prior points the human marked non-blocking and the code still leaves open: for the reply. */
134
+ answerInReply: PriorPoint[];
135
+ /** Findings with no wrong outcome and no cost (an opinion): shown, never a block, however many judges agree. */
136
+ notes: OpenItem[];
137
+ }
138
+ /** Everything the reviewers reported blocks; in delta mode, previous open items carry unless resolved with evidence. */
139
+ export declare function account(verdict: Verdict, previousOpen: OpenItem[] | undefined, verify: Verify): Accounting;
140
+ export declare function itemLine(item: OpenItem): string;
141
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * A reviewer's answer as a verdict, several verdicts as one, and the open items a verdict leaves:
3
+ * each with a stable id, so a later run, the branch record and a reply all name the same thing.
4
+ * Fails closed: an API error, a timeout, a crash or a malformed answer is never a pass. In delta
5
+ * mode nothing from the previous verdict disappears silently: an item the reviewer neither carried
6
+ * nor resolved with evidence stays open, marked "not accounted for". Every item that names code is
7
+ * checked against the checkout before it can block: a file not in the repository, or a line past
8
+ * its end, is a reviewer's slip, reported apart and never a block.
9
+ */
10
+ import { createHash } from 'crypto';
11
+ const SHAPE = ['prior_points', 'reads', 'findings'];
12
+ const LISTS = ['redundant', 'scans', 'merge_impact', 'carried', 'resolved_previous'];
13
+ /** The verdict in a reviewer's answer, or why it is not one. `needsPriorPoints`: a human review exists and none of its points is carried. */
14
+ export function parseVerdict(text, needsPriorPoints, reviewer, spend) {
15
+ const parsed = verdictIn(text);
16
+ if (!parsed || !SHAPE.every(key => Array.isArray(parsed[key])))
17
+ return { error: `${reviewer}: no valid verdict (${text.slice(0, 200).replace(/\s+/g, ' ')})` };
18
+ if (needsPriorPoints && parsed.prior_points.length === 0)
19
+ return { error: `${reviewer} did not report on the human reviews` };
20
+ for (const key of LISTS)
21
+ if (!Array.isArray(parsed[key]))
22
+ parsed[key] = [];
23
+ return { verdict: { ...parsed, reviewer, ...(spend.costUsd !== undefined ? { cost_usd: spend.costUsd } : {}), ...(spend.tokens ? { tokens: spend.tokens } : {}) } };
24
+ }
25
+ /** The whole answer, a fenced block, or the last object that starts with "prior_points" (a model sometimes writes a summary around it). */
26
+ function verdictIn(text) {
27
+ const candidates = [
28
+ text.trim(),
29
+ ...[...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map(m => m[1].trim()).reverse(),
30
+ ...[...text.matchAll(/\{\s*"prior_points"/g)].map(m => text.slice(m.index)).reverse(),
31
+ ];
32
+ for (const candidate of candidates) {
33
+ for (let end = candidate.lastIndexOf('}'); end > 0; end = candidate.lastIndexOf('}', end - 1)) {
34
+ try {
35
+ const parsed = JSON.parse(candidate.slice(0, end + 1));
36
+ if (parsed && typeof parsed === 'object' && 'prior_points' in parsed)
37
+ return parsed;
38
+ }
39
+ catch {
40
+ // not a whole object yet: try a shorter one
41
+ }
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+ /** Several reviewers as one: a prior point is resolved only when every reviewer says so; every other item stands, tagged with who found it. */
47
+ export function mergeVerdicts(parts) {
48
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 80);
49
+ const prior = new Map();
50
+ for (const part of parts) {
51
+ for (const point of part.prior_points) {
52
+ const key = norm(point.point);
53
+ const current = prior.get(key);
54
+ if (!current || (current.resolved && !point.resolved))
55
+ prior.set(key, { ...point, reviewer: part.reviewer });
56
+ }
57
+ }
58
+ const tagged = (pick) => parts.flatMap(part => pick(part).map(item => ({ ...item, reviewer: part.reviewer })));
59
+ return {
60
+ prior_points: [...prior.values()],
61
+ redundant: tagged(part => part.redundant),
62
+ reads: tagged(part => part.reads),
63
+ scans: tagged(part => part.scans),
64
+ merge_impact: tagged(part => part.merge_impact),
65
+ findings: tagged(part => part.findings),
66
+ carried: parts.flatMap(part => part.carried),
67
+ resolved_previous: parts.length === 1 ? parts[0].resolved_previous : parts[0].resolved_previous.filter(x => parts.every(part => part.resolved_previous.some(y => y.id === x.id))),
68
+ reviewers: parts.map(part => ({ reviewer: part.reviewer ?? '?', ...(part.cost_usd !== undefined ? { cost_usd: part.cost_usd } : {}), ...(part.tokens ? { tokens: part.tokens } : {}) })),
69
+ };
70
+ }
71
+ /**
72
+ * Delta mode: a human point the previous verdict resolved stays resolved unless the new commits
73
+ * touch a file its evidence names; it is carried into the new verdict so nothing disappears, and
74
+ * the reviewer is not paid to judge it again.
75
+ */
76
+ export function carryResolved(verdict, previous, touched) {
77
+ if (!previous)
78
+ return verdict;
79
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 80);
80
+ const judged = new Set(verdict.prior_points.map(p => norm(p.point)));
81
+ const carried = previous.prior_points.filter(p => p.resolved && !judged.has(norm(p.point)) && !evidenceTouched(p.evidence, touched));
82
+ return carried.length ? { ...verdict, prior_points: [...verdict.prior_points, ...carried] } : verdict;
83
+ }
84
+ /** True when the evidence names a file in the set (`src/a.ts:12`, `src/a.ts`). */
85
+ export function evidenceTouched(evidence, touched) {
86
+ for (const match of (evidence ?? '').matchAll(/[\w./-]+\.[A-Za-z]+/g))
87
+ if (touched.has(match[0]))
88
+ return true;
89
+ return false;
90
+ }
91
+ const id = (...parts) => createHash('sha1').update(parts.map(p => String(p ?? '')).join('|').toLowerCase().replace(/\s+/g, ' ')).digest('hex').slice(0, 10);
92
+ /** Everything the reviewers reported blocks; in delta mode, previous open items carry unless resolved with evidence. */
93
+ export function account(verdict, previousOpen, verify) {
94
+ const open = [];
95
+ const unverified = [];
96
+ const seen = new Set();
97
+ // A prior point is the human's and needs no file; anything else must name code the checkout has.
98
+ const add = (item) => {
99
+ if (seen.has(item.id))
100
+ return;
101
+ seen.add(item.id);
102
+ const placed = item.kind === 'prior' || (!!item.file && verify(item.file, item.line));
103
+ (placed ? open : unverified).push(item);
104
+ };
105
+ const answerInReply = [];
106
+ for (const p of verdict.prior_points) {
107
+ if (p.resolved)
108
+ continue;
109
+ if (p.severity === 'non-blocking')
110
+ answerInReply.push(p);
111
+ else
112
+ add({ id: id('prior', p.review, p.point), kind: 'prior', class: 'prior point', issue: p.point, evidence: p.evidence, reviewer: p.reviewer });
113
+ }
114
+ for (const r of verdict.redundant) {
115
+ if (r.removed === false)
116
+ add({ id: id('dead-code', r.file, r.what), kind: 'redundant', class: 'dead-code', file: r.file, line: r.line, issue: r.what, evidence: r.made_redundant_by ? `made redundant by ${r.made_redundant_by}` : undefined, reviewer: r.reviewer });
117
+ }
118
+ for (const r of verdict.reads) {
119
+ const problems = [
120
+ ...(r.rules ?? []).filter(x => x.known_before_read && x.applied_before_read !== true).map(x => ({ cls: 'production-cost', text: `known before the read, applied after: ${x.rule}` })),
121
+ ...(r.narrower_source ? [{ cls: 'production-cost', text: `narrower source: ${r.narrower_source}` }] : []),
122
+ ...(r.window_bounded === false ? [{ cls: 'production-cost', text: 'window not bounded at both ends' }] : []),
123
+ ...(r.keyset === false ? [{ cls: 'production-cost', text: 'OFFSET paging' }] : []),
124
+ ...(r.keys ?? []).filter(k => k.stable_under_edit === false).map(k => ({ cls: 'correctness', text: `key ${k.name} changes when the user edits: ${k.inputs}` })),
125
+ ];
126
+ for (const p of problems)
127
+ add({ id: id(p.cls, r.file, r.read, p.text), kind: 'read', class: p.cls, file: r.file, line: r.line, issue: `${r.read}: ${p.text}`, reviewer: r.reviewer });
128
+ }
129
+ for (const s of verdict.scans)
130
+ add({ id: id('production-cost', s.file, s.function), kind: 'scan', class: 'production-cost', file: s.file, line: s.line, issue: `${s.function} scans ${s.inner} per item of ${s.outer}`, evidence: s.fix, reviewer: s.reviewer });
131
+ for (const m of verdict.merge_impact) {
132
+ if (m.holds === false)
133
+ add({ id: id('correctness', m.call_site, m.symbol), kind: 'merge', class: 'correctness', file: m.call_site.split(':')[0], line: Number(m.call_site.split(':')[1]) || undefined, issue: `${m.symbol} from ${m.main_file} changed in main: ${m.why}`, reviewer: m.reviewer });
134
+ }
135
+ const notes = [];
136
+ const stillOpen = new Set((previousOpen ?? []).map(item => item.id));
137
+ for (const f of verdict.findings) {
138
+ const item = { id: id(f.class, f.file, f.issue), kind: 'finding', class: f.class, file: f.file, line: f.line, issue: f.issue, evidence: f.why, ...(f.consequence?.trim() ? { consequence: f.consequence.trim() } : {}), reviewer: f.reviewer };
139
+ // An opinion is a finding the judge said has no consequence: an empty one. A missing field fails closed, and an
140
+ // item already open from the last round stays open until it is resolved with evidence.
141
+ const opinion = typeof f.consequence === 'string' && !f.consequence.trim() && !stillOpen.has(item.id);
142
+ if (!opinion)
143
+ add(item);
144
+ else if (!notes.some(n => n.id === item.id))
145
+ notes.push(item);
146
+ }
147
+ const resolved = [];
148
+ if (previousOpen) {
149
+ const evidence = new Map(verdict.resolved_previous.filter(r => r?.id && r.evidence).map(r => [r.id, r.evidence]));
150
+ for (const item of previousOpen) {
151
+ if (evidence.has(item.id))
152
+ resolved.push({ item, evidence: evidence.get(item.id) });
153
+ else if (!seen.has(item.id)) {
154
+ seen.add(item.id);
155
+ open.push({ ...item, status: verdict.carried.includes(item.id) ? 'carried' : 'not accounted for' });
156
+ }
157
+ }
158
+ }
159
+ return { open, unverified, resolved, answerInReply, notes };
160
+ }
161
+ export function itemLine(item) {
162
+ const where = item.file ? ` ${item.file}${item.line ? `:${item.line}` : ''}` : '';
163
+ const by = item.reviewer ? ` (${item.reviewer})` : '';
164
+ const status = item.status ? `, ${item.status}` : '';
165
+ return `[${item.class}${status}]${by}${where} ${item.issue}${item.consequence ? `\n consequence: ${item.consequence}` : ''}${item.evidence ? `\n ${item.evidence}` : ''}`;
166
+ }
@@ -1,55 +1,90 @@
1
1
  import type { Config } from '../types/index.js';
2
- export interface PriorPoint {
3
- point: string;
4
- resolved: boolean;
5
- evidence?: string;
6
- /** As the human marked it: false for a nit, a question, or a point they said was optional. Open non-blocking points are listed for the reply, not held against the push. */
7
- blocking?: boolean;
8
- }
9
- export interface BlockingIssue {
10
- file: string;
11
- line?: number;
12
- issue: string;
13
- why?: string;
14
- }
15
- export interface ReviewerVerdict {
16
- prior_points: PriorPoint[];
17
- blocking: BlockingIssue[];
18
- non_blocking: Array<{
19
- file: string;
20
- issue: string;
21
- }>;
2
+ import { type ReviewerName, type Tokens } from './reviewer/adapters.js';
3
+ import { type Exec, type Progress } from './reviewer/exec.js';
4
+ import { type PanelItem } from './reviewer/panel.js';
5
+ import { type RunChoice, type Source } from './reviewer/settings.js';
6
+ import { type Accounting, type OpenItem, type PriorPoint } from './reviewer/verdict.js';
7
+ export { defaultExec, githubEnv, parseJsonArrays, type Exec, type Progress } from './reviewer/exec.js';
8
+ export { itemLine, type OpenItem } from './reviewer/verdict.js';
9
+ export type ReviewerOutcome = 'passed' | 'findings' | 'unavailable' | 'skipped';
10
+ export interface ReviewerOptions {
11
+ /** The pull request to read when the checkout is detached (a backtest). */
12
+ pr?: number;
13
+ /** ISO time: a review or comment posted from then on is not shown to the reviewer (a backtest). */
14
+ reviewsBefore?: string;
15
+ /** At push the ready-pull-request rule applies; a review command or a backtest always reviews. */
16
+ trigger?: 'push' | 'review' | 'backtest';
17
+ /** Two vendors over the whole branch, never a delta: the step before requesting a human review. */
18
+ full?: boolean;
19
+ /** This run's own choice of mode and panel, the nearest layer of the settings (reviewer/settings.ts). */
20
+ choice?: RunChoice;
21
+ /** Review again even when a verdict for these inputs is cached. */
22
+ force?: boolean;
23
+ /** Deterministic hints (a nested scan, a property that only leaves through serialisation): candidates for the reviewer to confirm. */
24
+ hints?: string;
25
+ /** What Rigour's checks already found on this change: settled, so no judge spends a turn finding it again. */
26
+ checks?: string[];
27
+ /** Where the team's state lives (.rigour: dismissals, reviewed functions) when cwd is a worktree that lacks it. */
28
+ stateRoot?: string;
29
+ /** The branch the commit was pushed from, when reviewing it in a detached worktree (background.ts). */
30
+ branch?: string;
22
31
  }
23
32
  export interface ReviewerResult {
24
- verdict?: ReviewerVerdict;
25
- /** Why there is no verdict: the reviewer could not run or answered badly. Never a pass. */
26
- error?: string;
27
- /** The human review it worked from, if any (`Reviewer: <login>, <date>`). */
28
- previousReview?: string;
33
+ outcome: ReviewerOutcome;
34
+ /** What blocks: every open item, with a stable id. */
35
+ items: OpenItem[];
36
+ /** Items naming code the checkout does not have: shown, never a block. */
37
+ unverified: OpenItem[];
38
+ /** Previous open items resolved since the last verdict, with the evidence. */
39
+ resolved: Accounting['resolved'];
40
+ /** Non-blocking human points still open: answered in the reply, not held against the push. */
41
+ answerInReply: PriorPoint[];
42
+ /** Findings with no wrong outcome and no cost: shown, never a block. */
43
+ notes: OpenItem[];
44
+ /** Panel findings without a majority: shown, never a block, not carried to the next round. */
45
+ disputed: OpenItem[];
46
+ /** Panel findings refuted with evidence: logged, never a block. */
47
+ dropped: OpenItem[];
48
+ /** Findings the team dismissed as not a bug (`rigour dismiss <id>`), raised again: shown, never a block. */
49
+ dismissed: OpenItem[];
50
+ /** The panel's decision on every finding, with each judge's call: what a later re-scoring fits on. */
51
+ panel?: PanelItem[];
52
+ /** Why there is no verdict (unavailable) or why none was sought (skipped). */
53
+ reason?: string;
54
+ reviewers: ReviewerName[];
55
+ scope?: 'full' | 'delta';
56
+ why?: string;
57
+ costUsd?: number;
58
+ /** Tokens every run reported, summed: the only measure of a CLI that reports no dollars (Codex). */
59
+ tokens?: Tokens;
60
+ /** Whether the team lets people dismiss these findings (review.reviewer.dismissals). */
61
+ dismissable?: boolean;
62
+ /** Agent runs behind this verdict: one per judge, plus each cross-examination. */
63
+ runs?: number;
64
+ /** The mode asked for, the one that ran, where the choice came from, and why it ran with fewer judges. */
65
+ mode?: ModeRecord;
29
66
  cached: boolean;
67
+ /** The latest human review it worked from (`<login>, <date> (<n> reviews)`). */
68
+ previousReview?: string;
69
+ pr?: number;
70
+ }
71
+ export interface ModeRecord {
72
+ asked: 'single' | 'cross' | 'full' | 'panel';
73
+ ran: 'single' | 'cross' | 'full' | 'panel';
74
+ source: Source;
75
+ /** Why fewer judges ran than were asked for. */
76
+ degraded?: string;
77
+ /** Nearer choices that were not applied, such as a user turning off a panel the team requires. */
78
+ refused?: string[];
79
+ /** With escalate: risk, why this review got one judge or all of them. */
80
+ escalation?: string;
30
81
  }
31
- /** How commands are run; tests replace it. */
32
- export type Exec = (command: string, args: string[], options: {
33
- cwd: string;
34
- timeoutMs: number;
35
- env?: Record<string, string>;
36
- }) => Promise<{
37
- exitCode: number;
38
- stdout: string;
39
- stderr: string;
40
- }>;
41
- /** Called while the reviewer works, so a slow run and a stuck one look different. */
42
- export type Progress = (message: string) => void;
43
- export declare function runReviewer(cwd: string, base: string, config: Config, exec?: Exec, progress?: Progress): Promise<ReviewerResult>;
44
- /** Open blocking points and new blocking issues: what stops a push. An open non-blocking point is answered in the reply, not held against the push. */
82
+ /** Findings and no verdict stop a push; a skipped review does not, and is reported as owed. */
45
83
  export declare function reviewerBlocks(result: ReviewerResult): boolean;
46
- /** The reviewer's answer as a verdict, or why it is not one. */
47
- export declare function parseVerdict(answer: {
48
- exitCode: number;
49
- stdout: string;
50
- stderr: string;
51
- }, hadReview: boolean): {
52
- verdict: ReviewerVerdict;
53
- } | {
54
- error: string;
55
- };
84
+ /** The reviewer, and one anonymous usage event for what it did (only when the person opted in to telemetry). */
85
+ export declare function runReviewer(cwd: string, base: string, config: Config, exec?: Exec, progress?: Progress, options?: ReviewerOptions): Promise<ReviewerResult>;
86
+ /**
87
+ * Before any checkout or typed review: whether this push gets a model review at all (an open, ready
88
+ * pull request, `on_push`). The background job asks first, so a push nobody will read costs one lookup.
89
+ */
90
+ export declare function pushReviewSkip(cwd: string, branch: string, head: string, config: Config, exec?: Exec): Promise<string | undefined>;