@rigour-labs/core 6.9.0-rc.2 → 6.9.0-rc.3

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.
@@ -46,7 +46,7 @@ export async function learnFromReviews(cwd, options) {
46
46
  const byNumber = new Map(prs.filter(pr => pr.mergedAt).map(pr => [pr.number, pr]));
47
47
  const reverts = new Map([...byNumber.values()].map(pr => [pr.number, revertOf(git, pr, { mainRef: options.mainRef, until: options.until })]));
48
48
  for (const lesson of merged.lessons) {
49
- if (lesson.state !== 'candidate' || lesson.evidence.some(e => e.kind === 'outcome' || e.kind === 'counter'))
49
+ if (lesson.state !== 'candidate' || lesson.evidence.some(e => e.kind === 'outcome' || e.kind === 'lines' || e.kind === 'counter'))
50
50
  continue;
51
51
  for (const point of lesson.evidence.filter(e => (e.kind ?? 'point') === 'point')) {
52
52
  const pr = byNumber.get(point.pr);
@@ -54,7 +54,8 @@ export async function learnFromReviews(cwd, options) {
54
54
  continue;
55
55
  const found = (point.actedOn === false ? reverts.get(pr.number) : undefined) ?? outcomeFor(git, lesson, pr, { mainRef: options.mainRef, until: options.until, windowDays: options.windowDays });
56
56
  if (found) {
57
- lesson.evidence.push(found);
57
+ // A fix on the point's lines, or a revert, is evidence for a person to promote in Studio, never a promotion (lessonState).
58
+ lesson.evidence.push(found.kind === 'outcome' ? { ...found, kind: 'lines' } : found);
58
59
  break;
59
60
  }
60
61
  }
@@ -12,12 +12,13 @@ import type { Git, ReviewBody, ReviewComment } from './acted-on.js';
12
12
  * lines a later fix changed the point's own lines (outcome-evidence.ts): shown for a person to promote or dismiss,
13
13
  * never a promotion on its own (a fix on the same lines is often unrelated work);
14
14
  * dismissed a person looked at that evidence and set it aside: the lesson stays as it was;
15
+ * reclassified a lesson an outcome alone had promoted, back to a candidate when outcomes stopped promoting (once);
15
16
  * against a later pull request a review found repeating the lesson merged anyway and settled clean;
16
17
  * demoted enough independent `against` pull requests took back a lesson evidence had promoted: a candidate again,
17
18
  * until a person promotes it.
18
19
  * A record from before evidence kinds has none: it is a `point`.
19
20
  */
20
- export type EvidenceKind = 'point' | 'outcome' | 'counter' | 'correction' | 'accepted' | 'rejected' | 'norule' | 'followup' | 'lines' | 'dismissed' | 'against' | 'demoted';
21
+ export type EvidenceKind = 'point' | 'outcome' | 'counter' | 'correction' | 'accepted' | 'rejected' | 'norule' | 'followup' | 'lines' | 'dismissed' | 'against' | 'demoted' | 'reclassified';
21
22
  export interface LessonEvidence {
22
23
  kind?: EvidenceKind;
23
24
  pr: number;
@@ -59,9 +60,9 @@ export interface ReviewLesson {
59
60
  updatedAt: string;
60
61
  }
61
62
  /**
62
- * A lesson's state from its evidence trail. A person's decision wins; then an outcome; counter-evidence
63
- * holds a candidate back; recurrence across pull requests and authors is enough only without it. A record
64
- * from before evidence kinds keeps the state it had.
63
+ * A lesson's state from its evidence trail. A person's decision wins; then a person's correction; counter-evidence
64
+ * holds a candidate back; recurrence across pull requests and authors is enough only without it. An outcome is
65
+ * evidence, never a promotion. A record from before evidence kinds keeps the state it had.
65
66
  */
66
67
  export declare function lessonState(lesson: ReviewLesson): Pick<ReviewLesson, 'state' | 'promotedBy'>;
67
68
  /** The point of a review comment: its bold title, else its first sentence, without tool output or markup. */
@@ -35,9 +35,9 @@ const PLAIN_WORDS = new Set(['when', 'that', 'this', 'with', 'from', 'before', '
35
35
  const NOT_CODE = /(\.(md|mdx|txt|ya?ml|json|lock|snap)$)|(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(__tests__|docs?|migrations|\.github)\//i;
36
36
  const NOT_SYMBOLS = new Set(['this', 'that', 'with', 'from', 'return', 'const', 'await', 'async', 'function', 'true', 'false', 'null', 'undefined', 'string', 'number', 'boolean', 'export', 'import', 'type', 'interface', 'else', 'when', 'then', 'should', 'line', 'lines']);
37
37
  /**
38
- * A lesson's state from its evidence trail. A person's decision wins; then an outcome; counter-evidence
39
- * holds a candidate back; recurrence across pull requests and authors is enough only without it. A record
40
- * from before evidence kinds keeps the state it had.
38
+ * A lesson's state from its evidence trail. A person's decision wins; then a person's correction; counter-evidence
39
+ * holds a candidate back; recurrence across pull requests and authors is enough only without it. An outcome is
40
+ * evidence, never a promotion. A record from before evidence kinds keeps the state it had.
41
41
  */
42
42
  export function lessonState(lesson) {
43
43
  const kinds = new Set(lesson.evidence.map(e => e.kind));
@@ -56,8 +56,8 @@ export function lessonState(lesson) {
56
56
  return { state: 'candidate' };
57
57
  if (kinds.has('correction'))
58
58
  return { state: 'verified', promotedBy: 'correction' };
59
- if (kinds.has('outcome'))
60
- return { state: 'verified', promotedBy: 'outcome' };
59
+ // An outcome (a later fix on the point's lines, a revert) no longer promotes on its own: judged by a person against
60
+ // real history, it was most often unrelated work. It is shown in Studio for a person to promote.
61
61
  if (kinds.has('counter'))
62
62
  return { state: 'candidate' };
63
63
  const points = lesson.evidence.filter(e => (e.kind ?? 'point') === 'point');
@@ -227,12 +227,29 @@ export function lessonsPath(cwd) {
227
227
  export function readLessons(cwd) {
228
228
  try {
229
229
  const parsed = JSON.parse(fs.readFileSync(lessonsPath(cwd), 'utf8'));
230
- return Array.isArray(parsed?.lessons) ? parsed.lessons : [];
230
+ return Array.isArray(parsed?.lessons) ? parsed.lessons.map(reclassified) : [];
231
231
  }
232
232
  catch {
233
233
  return [];
234
234
  }
235
235
  }
236
+ /** Why a lesson an outcome alone had promoted is a candidate again. */
237
+ const RECLASSIFIED = 'promoted by the exact-line rule, which no longer promotes on its own';
238
+ /**
239
+ * A lesson stored as verified by an outcome (a later fix on its lines, or a revert), as every reader sees it now that
240
+ * outcomes no longer promote: its state worked out again from its evidence, and, when that leaves it a candidate, one
241
+ * `reclassified` record saying why. Never deleted, nothing else changed; the next write keeps it, and a lesson that
242
+ * already has the record is left as it is. Recurrence, a correction or a person's decision keeps a lesson verified.
243
+ */
244
+ function reclassified(lesson) {
245
+ if (lesson.state !== 'verified' || lesson.promotedBy !== 'outcome' || lesson.evidence.some(e => e.kind === 'reclassified'))
246
+ return lesson;
247
+ const next = lessonState(lesson);
248
+ if (next.state === 'verified')
249
+ return { ...lesson, ...next };
250
+ const at = new Date().toISOString();
251
+ return { ...lesson, state: 'candidate', promotedBy: undefined, evidence: [...lesson.evidence, { kind: 'reclassified', pr: lesson.evidence[0]?.pr ?? 0, comment: `reclassified-${lesson.id}`, author: '', detail: RECLASSIFIED, at }] };
252
+ }
236
253
  export function writeLessons(cwd, lessons) {
237
254
  const file = lessonsPath(cwd);
238
255
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -13,8 +13,8 @@
13
13
  * lesson warns against; the reviewer's lessons step, review event `lessons_applied`) and the pull request merged anyway
14
14
  * and settled clean: CI passed on the merge commit, no fix touched the lesson's file in the window, and no revert. A
15
15
  * pull request that followed the lesson, or that no review checked against it, never counts. `demote_after` such pull
16
- * requests, independent (more than one author, or merged at least a week apart), take back a lesson evidence promoted
17
- * (by an outcome or by recurrence): it is `demoted` to a candidate. A lesson a person promoted, or corrected into being,
16
+ * requests, independent (more than one author, or merged at least a week apart), take back a lesson recurrence
17
+ * promoted: it is `demoted` to a candidate. A lesson a person promoted, or corrected into being,
18
18
  * is not.
19
19
  */
20
20
  import type { PrOutcome } from '../outcomes/outcome.js';
@@ -44,7 +44,7 @@ export function applyOutcomeEvidence(lessons, records, applied, options) {
44
44
  }
45
45
  }
46
46
  // Against it: later pull requests a review found repeating it, merged anyway, settled clean.
47
- if (lesson.state === 'verified' && (lesson.promotedBy === 'outcome' || lesson.promotedBy === 'recurrence')) {
47
+ if (lesson.state === 'verified' && lesson.promotedBy === 'recurrence') {
48
48
  for (const [pr, ids] of applied) {
49
49
  const record = byPr.get(pr);
50
50
  if (own.has(pr) || !ids.has(lesson.id) || !record || !settledClean(record, lesson.file))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.9.0-rc.2",
3
+ "version": "6.9.0-rc.3",
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-arm64": "6.9.0-rc.2",
79
- "@rigour-labs/brain-darwin-x64": "6.9.0-rc.2",
80
- "@rigour-labs/brain-linux-arm64": "6.9.0-rc.2",
81
- "@rigour-labs/brain-linux-x64": "6.9.0-rc.2",
82
- "@rigour-labs/brain-win-x64": "6.9.0-rc.2"
78
+ "@rigour-labs/brain-darwin-arm64": "6.9.0-rc.3",
79
+ "@rigour-labs/brain-darwin-x64": "6.9.0-rc.3",
80
+ "@rigour-labs/brain-linux-arm64": "6.9.0-rc.3",
81
+ "@rigour-labs/brain-win-x64": "6.9.0-rc.3",
82
+ "@rigour-labs/brain-linux-x64": "6.9.0-rc.3"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/fs-extra": "^11.0.4",