@rigour-labs/core 6.11.2-rc.1 → 6.12.0-rc.1
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.
- package/dist/brief/briefing.d.ts +2 -1
- package/dist/brief/briefing.js +7 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/review-learning/lessons.d.ts +11 -1
- package/dist/review-learning/lessons.js +38 -3
- package/dist/review-learning/team-lessons.d.ts +1 -0
- package/dist/review-learning/team-lessons.js +5 -2
- package/package.json +6 -6
package/dist/brief/briefing.d.ts
CHANGED
|
@@ -36,7 +36,8 @@ export declare function buildBriefing(cwd: string, input?: BriefingInput): Brief
|
|
|
36
36
|
export declare function briefingText(briefing: Briefing): string;
|
|
37
37
|
/**
|
|
38
38
|
* The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
|
|
39
|
-
* lessons the team learned on it,
|
|
39
|
+
* lessons the team learned on it, then those a person widened to its folder or the whole repository, and the points
|
|
40
|
+
* the team settled against on it; at most three. At session start the
|
|
40
41
|
* task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
|
|
41
42
|
*/
|
|
42
43
|
export declare function buildFileBriefing(cwd: string, file: string, input?: {
|
package/dist/brief/briefing.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* matchers, before the code exists instead of after. It is deterministic (no model call), local, and it never blocks.
|
|
8
8
|
*/
|
|
9
9
|
import { spawnSync } from 'child_process';
|
|
10
|
+
import path from 'path';
|
|
10
11
|
import { rulesForDiff } from '../review-learning/repo-rules.js';
|
|
11
12
|
import { describeLesson, lessonsForDiff, lessonView, rejectedForDiff } from '../review-learning/team-lessons.js';
|
|
12
13
|
import { appendTaskEvent, taskOf } from '../task/thread.js';
|
|
@@ -52,7 +53,8 @@ export function briefingText(briefing) {
|
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
55
|
* The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
|
|
55
|
-
* lessons the team learned on it,
|
|
56
|
+
* lessons the team learned on it, then those a person widened to its folder or the whole repository, and the points
|
|
57
|
+
* the team settled against on it; at most three. At session start the
|
|
56
58
|
* task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
|
|
57
59
|
*/
|
|
58
60
|
export function buildFileBriefing(cwd, file, input = {}) {
|
|
@@ -61,12 +63,14 @@ export function buildFileBriefing(cwd, file, input = {}) {
|
|
|
61
63
|
const shape = shapeOf([file], '');
|
|
62
64
|
const mode = input.lessons ?? 'verified';
|
|
63
65
|
const rules = rulesForDiff(cwd, shape, true, BRIEFING_MAX_ITEMS, true).filter(r => r.requirement);
|
|
64
|
-
const
|
|
66
|
+
const reaches = (l) => l.file === file || l.scope === 'repo' || (l.scope === 'folder' && file.startsWith(`${path.posix.dirname(l.file)}/`));
|
|
67
|
+
const lessons = lessonsForDiff(cwd, shape, mode, 0, BRIEFING_MAX_ITEMS, BRIEFING_MAX_ITEMS).filter(l => reaches(l) && (l.state === 'verified' || mode === 'all'));
|
|
65
68
|
const settled = rejectedForDiff(cwd, shape).filter(l => l.file === file);
|
|
66
69
|
const cited = (prs) => (prs.length ? `learned in PR ${prs.map(p => `#${p}`).join(', ')}` : 'the team\'s decision');
|
|
67
70
|
const items = [
|
|
68
71
|
...rules.map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), requirement: true, id: `rule:${r.id}` })),
|
|
69
|
-
|
|
72
|
+
// This file's own lessons read as they are; a widened one says where it reaches (a folder, the team).
|
|
73
|
+
...lessons.map(l => ({ kind: 'lesson', text: l.file === file && !l.scope ? lessonView(l).text : describeLesson({ ...lessonView(l), prs: [] }), cite: cited(lessonView(l).prs), id: `lesson:${l.id}` })),
|
|
70
74
|
...settled.map(l => ({ kind: 'settled', text: `settled against, do not do or raise it: ${lessonView(l).text}`, cite: cited(lessonView(l).prs), id: `settled:${l.id}` })),
|
|
71
75
|
];
|
|
72
76
|
return { ...(task ? { task: task.key } : {}), goal: '', files: [file], items: items.slice(0, limit) };
|
package/dist/index.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export { decideCompiledCheck, proposeCompiledChecks, readCompiledChecks, suspens
|
|
|
49
49
|
export { ruleWriterFor } from './review/reviewer/rule-writer.js';
|
|
50
50
|
export { backtestLast, formatLast, scoreLast, LAST_LEDGER_PATH, type LastReport } from './review/backtest-last.js';
|
|
51
51
|
export { buildRecord, recordLines, recordIntact, type ReviewRecord } from './review/reviewer/record.js';
|
|
52
|
-
export { readLessons, writeLessons, decideLesson, acceptSuggestedText, lessonState, pendingDecision, matchLessons, lessonText, lessonsPath, type ReviewLesson, type LessonEvidence } from './review-learning/lessons.js';
|
|
52
|
+
export { readLessons, writeLessons, decideLesson, acceptSuggestedText, scopeLesson, lessonState, pendingDecision, matchLessons, lessonText, lessonsPath, type ReviewLesson, type LessonEvidence } from './review-learning/lessons.js';
|
|
53
53
|
export { lessonsForDiff, lessonsSection, type LessonMode } from './review-learning/team-lessons.js';
|
|
54
54
|
export { recordAgentWrites, captureHumanEdits } from './review-learning/human-edits.js';
|
|
55
55
|
export { readRepoRules, rulesForDiff, rulesSection, splitRules, type RepoRule } from './review-learning/repo-rules.js';
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ export { decideCompiledCheck, proposeCompiledChecks, readCompiledChecks, suspens
|
|
|
47
47
|
export { ruleWriterFor } from './review/reviewer/rule-writer.js';
|
|
48
48
|
export { backtestLast, formatLast, scoreLast, LAST_LEDGER_PATH } from './review/backtest-last.js';
|
|
49
49
|
export { buildRecord, recordLines, recordIntact } from './review/reviewer/record.js';
|
|
50
|
-
export { readLessons, writeLessons, decideLesson, acceptSuggestedText, lessonState, pendingDecision, matchLessons, lessonText, lessonsPath } from './review-learning/lessons.js';
|
|
50
|
+
export { readLessons, writeLessons, decideLesson, acceptSuggestedText, scopeLesson, lessonState, pendingDecision, matchLessons, lessonText, lessonsPath } from './review-learning/lessons.js';
|
|
51
51
|
export { lessonsForDiff, lessonsSection } from './review-learning/team-lessons.js';
|
|
52
52
|
export { recordAgentWrites, captureHumanEdits } from './review-learning/human-edits.js';
|
|
53
53
|
export { readRepoRules, rulesForDiff, rulesSection, splitRules } from './review-learning/repo-rules.js';
|
|
@@ -18,7 +18,7 @@ import type { Git, ReviewBody, ReviewComment } from './acted-on.js';
|
|
|
18
18
|
* until a person promotes it.
|
|
19
19
|
* A record from before evidence kinds has none: it is a `point`.
|
|
20
20
|
*/
|
|
21
|
-
export type EvidenceKind = 'point' | 'outcome' | 'counter' | 'correction' | 'accepted' | 'rejected' | 'norule' | 'followup' | 'lines' | 'dismissed' | 'against' | 'demoted' | 'reclassified' | 'compiled' | 'reworded';
|
|
21
|
+
export type EvidenceKind = 'point' | 'outcome' | 'counter' | 'correction' | 'accepted' | 'rejected' | 'norule' | 'followup' | 'lines' | 'dismissed' | 'against' | 'demoted' | 'reclassified' | 'compiled' | 'reworded' | 'scoped';
|
|
22
22
|
export interface LessonEvidence {
|
|
23
23
|
kind?: EvidenceKind;
|
|
24
24
|
pr: number;
|
|
@@ -49,6 +49,11 @@ export interface ReviewLesson {
|
|
|
49
49
|
state: 'candidate' | 'verified' | 'rejected';
|
|
50
50
|
/** Which evidence made it a lesson. */
|
|
51
51
|
promotedBy?: 'outcome' | 'correction' | 'person' | 'recurrence' | 'legacy';
|
|
52
|
+
/**
|
|
53
|
+
* How far a person widened it (scopeLesson): `folder`, every change in the lesson's folder; `repo`, every change,
|
|
54
|
+
* a standard for the whole repository. Absent: its file (or, with no file, a standard served by its words).
|
|
55
|
+
*/
|
|
56
|
+
scope?: 'folder' | 'repo';
|
|
52
57
|
evidence: LessonEvidence[];
|
|
53
58
|
/**
|
|
54
59
|
* A corrected wording a newer version derived from the lesson's own comment, waiting for a person. A lesson
|
|
@@ -116,6 +121,11 @@ export declare function matchLessons(lessons: ReviewLesson[], change: ChangeShap
|
|
|
116
121
|
perFile?: number;
|
|
117
122
|
excludePr?: number;
|
|
118
123
|
}): ReviewLesson[];
|
|
124
|
+
/**
|
|
125
|
+
* A person sets how far a lesson reaches: `repo` (every change), `folder` (every change in its folder) or `file` (its
|
|
126
|
+
* own file, as learned). Recorded as evidence with who and why; it says nothing about whether the lesson is right.
|
|
127
|
+
*/
|
|
128
|
+
export declare function scopeLesson(cwd: string, id: string, scope: 'file' | 'folder' | 'repo', by: string, why?: string): ReviewLesson | undefined;
|
|
119
129
|
/** RIGOUR_REVIEW_LESSONS points at a lessons file outside the clone (CI, or a team's shared copy). */
|
|
120
130
|
export declare function lessonsPath(cwd: string): string;
|
|
121
131
|
export declare function readLessons(cwd: string): ReviewLesson[];
|
|
@@ -28,6 +28,8 @@ const RECUR_PRS = 2;
|
|
|
28
28
|
const RECUR_AUTHORS = 2;
|
|
29
29
|
/** Team standards served with a change, on top of the lessons about its files (an agent's question; a judge takes more), and the words they must share with it. */
|
|
30
30
|
const MAX_STANDARDS = 3;
|
|
31
|
+
/** Repository standards (scope `repo`) served with every change, most-raised first. */
|
|
32
|
+
const REPO_STANDARDS = 10;
|
|
31
33
|
const STANDARD_WORDS = 2;
|
|
32
34
|
/** Words that say nothing about what a rule or a change is about. */
|
|
33
35
|
const PLAIN_WORDS = new Set(['when', 'that', 'this', 'with', 'from', 'before', 'after', 'every', 'their', 'them', 'than', 'each', 'only', 'into', 'rather', 'instead', 'should', 'make', 'keep', 'sure', 'does', 'what', 'which', 'there', 'these', 'those', 'such', 'more', 'other', 'same', 'then', 'also', 'both', 'must', 'never', 'always', 'once', 'been', 'have', 'will', 'your', 'about', 'over', 'under', 'change', 'changes', 'code', 'value', 'values', 'data', 'true', 'false', 'null', 'undefined', 'return', 'const', 'function', 'export', 'import', 'await', 'async', 'string', 'number', 'type']);
|
|
@@ -233,12 +235,20 @@ export function matchLessons(lessons, change, options = {}) {
|
|
|
233
235
|
// A lesson whose only evidence is the pull request under review is already in front of the judge as the reviewer's own points.
|
|
234
236
|
if (options.excludePr !== undefined)
|
|
235
237
|
lessons = lessons.filter(l => !l.evidence.length || l.evidence.some(e => e.pr !== options.excludePr));
|
|
238
|
+
const inPlay = (l) => l.state === 'verified' || (!!options.includeCandidates && l.state === 'candidate');
|
|
239
|
+
// A person made these the repository's standards: every change gets them, whatever its files or words; most-raised first.
|
|
240
|
+
const repo = lessons
|
|
241
|
+
.filter(l => l.scope === 'repo' && inPlay(l))
|
|
242
|
+
.sort((a, b) => prCount(b) - prCount(a) || b.evidence.length - a.evidence.length)
|
|
243
|
+
.slice(0, REPO_STANDARDS);
|
|
244
|
+
lessons = lessons.filter(l => l.scope !== 'repo');
|
|
236
245
|
const dirs = new Set(change.files.map(f => path.posix.dirname(f)));
|
|
246
|
+
const inFolder = (l) => l.scope === 'folder' && change.files.some(f => f.startsWith(`${path.posix.dirname(l.file)}/`));
|
|
237
247
|
const scored = lessons
|
|
238
|
-
.filter(l => !!l.file && (l
|
|
248
|
+
.filter(l => !!l.file && inPlay(l) && !NOT_CODE.test(l.file))
|
|
239
249
|
.map(l => ({
|
|
240
250
|
lesson: l,
|
|
241
|
-
score: (change.files.includes(l.file) ? 3 : 0) + (dirs.has(path.posix.dirname(l.file)) ? 0.5 : 0)
|
|
251
|
+
score: (change.files.includes(l.file) || inFolder(l) ? 3 : 0) + (dirs.has(path.posix.dirname(l.file)) ? 0.5 : 0)
|
|
242
252
|
+ 2 * l.symbols.filter(s => isSpecific(s) && change.symbols.has(s)).length,
|
|
243
253
|
}))
|
|
244
254
|
.filter(s => s.score >= 3)
|
|
@@ -266,7 +276,32 @@ export function matchLessons(lessons, change, options = {}) {
|
|
|
266
276
|
perFileCount.set(lesson.file, n + 1);
|
|
267
277
|
taken.push(lesson);
|
|
268
278
|
}
|
|
269
|
-
return [...taken, ...standards];
|
|
279
|
+
return [...taken, ...repo, ...standards];
|
|
280
|
+
}
|
|
281
|
+
/** Pull requests a lesson was raised or acted on in. */
|
|
282
|
+
function prCount(lesson) {
|
|
283
|
+
return new Set(lesson.evidence.filter(e => (e.kind ?? 'point') === 'point').map(e => e.pr)).size;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* A person sets how far a lesson reaches: `repo` (every change), `folder` (every change in its folder) or `file` (its
|
|
287
|
+
* own file, as learned). Recorded as evidence with who and why; it says nothing about whether the lesson is right.
|
|
288
|
+
*/
|
|
289
|
+
export function scopeLesson(cwd, id, scope, by, why = '') {
|
|
290
|
+
const lessons = readLessons(cwd);
|
|
291
|
+
const lesson = lessons.find(l => l.id === id);
|
|
292
|
+
if (!lesson)
|
|
293
|
+
return undefined;
|
|
294
|
+
if (scope === 'folder' && !lesson.file)
|
|
295
|
+
throw new Error('a team standard has no folder: scope it to the repo or leave it');
|
|
296
|
+
const at = new Date().toISOString();
|
|
297
|
+
if (scope === 'file')
|
|
298
|
+
delete lesson.scope;
|
|
299
|
+
else
|
|
300
|
+
lesson.scope = scope;
|
|
301
|
+
lesson.evidence.push({ kind: 'scoped', pr: lesson.evidence[0]?.pr ?? 0, comment: `scoped-${at}`, author: by, detail: why ? `${scope}: ${why}` : scope, at });
|
|
302
|
+
lesson.updatedAt = at;
|
|
303
|
+
writeLessons(cwd, lessons);
|
|
304
|
+
return lesson;
|
|
270
305
|
}
|
|
271
306
|
/** RIGOUR_REVIEW_LESSONS points at a lessons file outside the clone (CI, or a team's shared copy). */
|
|
272
307
|
export function lessonsPath(cwd) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* agent or a model) to check before the PR: "your team has asked for this
|
|
4
4
|
* before".
|
|
5
5
|
*/
|
|
6
|
+
import path from 'path';
|
|
6
7
|
import { parseDiff } from '../utils/diff.js';
|
|
7
8
|
import { matchLessons, readLessons } from './lessons.js';
|
|
8
9
|
/** Lessons a person or repeated evidence verified reach the reviewer unless a team turns them off. */
|
|
@@ -36,14 +37,16 @@ function changeShape(diff) {
|
|
|
36
37
|
}
|
|
37
38
|
export function lessonView(l) {
|
|
38
39
|
const said = l.evidence.find(e => e.said && e.said !== l.text)?.said;
|
|
39
|
-
return { file: l.file, text: l.text, prs: [...new Set(l.evidence.map(e => e.pr))], ...(said ? { said } : {}) };
|
|
40
|
+
return { file: l.file, text: l.text, prs: [...new Set(l.evidence.map(e => e.pr))], ...(said ? { said } : {}), ...(l.scope ? { scope: l.scope } : {}) };
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* One line per lesson: where it applies, the rule, and, when a model wrote the rule from a person's
|
|
43
44
|
* point, their own words, so anyone can see when the rule went beyond what was said.
|
|
44
45
|
*/
|
|
45
46
|
export function describeLesson(l) {
|
|
46
|
-
const where = l.file ?
|
|
47
|
+
const where = l.scope === 'repo' ? `team standard${l.file ? ` (learned on ${l.file})` : ''}: `
|
|
48
|
+
: l.scope === 'folder' ? `${path.posix.dirname(l.file)}/ (every file): `
|
|
49
|
+
: l.file ? `${l.file}: ` : 'team standard: ';
|
|
47
50
|
const said = l.said ? ` (in their words: "${l.said}")` : '';
|
|
48
51
|
const prs = l.prs.length ? ` (acted on in PR ${l.prs.map(p => `#${p}`).join(', ')})` : '';
|
|
49
52
|
return `${where}${l.text}${said}${prs}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.12.0-rc.1",
|
|
4
4
|
"description": "Rigour's review engine: deterministic gates on changed lines, rules and lessons learned from your team's fixes, and per-check precision from what you fix versus dismiss, across TypeScript, JavaScript, Python, Go, Ruby and C#.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13"
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"@anthropic-ai/sdk": "^0.132.1",
|
|
76
76
|
"pg": "^8.16.3",
|
|
77
77
|
"openai": "^5.23.2",
|
|
78
|
-
"@rigour-labs/brain-darwin-
|
|
79
|
-
"@rigour-labs/brain-
|
|
80
|
-
"@rigour-labs/brain-linux-arm64": "6.
|
|
81
|
-
"@rigour-labs/brain-
|
|
82
|
-
"@rigour-labs/brain-win-x64": "6.
|
|
78
|
+
"@rigour-labs/brain-darwin-x64": "6.12.0-rc.1",
|
|
79
|
+
"@rigour-labs/brain-linux-x64": "6.12.0-rc.1",
|
|
80
|
+
"@rigour-labs/brain-linux-arm64": "6.12.0-rc.1",
|
|
81
|
+
"@rigour-labs/brain-darwin-arm64": "6.12.0-rc.1",
|
|
82
|
+
"@rigour-labs/brain-win-x64": "6.12.0-rc.1"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/fs-extra": "^11.0.4",
|