@rigour-labs/cli 6.11.2 → 6.12.0-rc.2

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/cli.js CHANGED
@@ -403,6 +403,8 @@ program
403
403
  .option('--promote <id>', 'Accept a candidate as a lesson (recorded as your decision)')
404
404
  .option('--reject <id>', 'Reject a candidate or lesson: the judges are told this team decided against it')
405
405
  .option('--why <text>', 'Why, recorded with --promote or --reject')
406
+ .option('--scope <id>', 'Set how far a lesson reaches, with --to: its file, its folder, or every change (a team standard); recorded as your decision')
407
+ .option('--to <reach>', 'With --scope: file, folder or repo')
406
408
  .option('--use-wording <id>', 'Take the corrected wording a newer version suggests for a lesson you decided (the old wording is kept as evidence)')
407
409
  .option('--compile', 'Propose a deterministic check for each verified lesson a template fits, and list the compiled checks')
408
410
  .option('--approve-check <id>', 'Approve a compiled check: it runs on every review (a note unless gates.compiled_lessons.block)')
@@ -17,6 +17,9 @@ export interface LearnReviewsOptions {
17
17
  withdrawCheck?: string;
18
18
  /** A person takes the corrected wording a newer version suggested for a lesson they decided. */
19
19
  useWording?: string;
20
+ /** A person sets how far a lesson reaches (`to`: file, folder or repo). */
21
+ scope?: string;
22
+ to?: string;
20
23
  }
21
24
  export declare function learnReviewsCommand(cwd: string, options: LearnReviewsOptions): Promise<void>;
22
25
  /** owner/name from the origin remote (https or ssh, any host alias). */
@@ -11,7 +11,7 @@ import { execFileSync } from 'child_process';
11
11
  import chalk from 'chalk';
12
12
  import path from 'path';
13
13
  import { personOf } from './git-identity.js';
14
- import { acceptSuggestedText, branchBase, decideCompiledCheck, decideLesson, defaultExec, proposeCompiledChecks, readCompiledChecks, suspension, githubToken, learnFromReviews, lessonsPath, readLessons, ruleWriterFor, ConfigSchema } from '@rigour-labs/core';
14
+ import { acceptSuggestedText, scopeLesson, branchBase, decideCompiledCheck, decideLesson, defaultExec, proposeCompiledChecks, readCompiledChecks, suspension, githubToken, learnFromReviews, lessonsPath, readLessons, ruleWriterFor, ConfigSchema } from '@rigour-labs/core';
15
15
  import { loadConfig } from './review-config.js';
16
16
  export async function learnReviewsCommand(cwd, options) {
17
17
  if (options.promote)
@@ -22,6 +22,8 @@ export async function learnReviewsCommand(cwd, options) {
22
22
  return list(cwd, options.json);
23
23
  if (options.useWording)
24
24
  return useWording(cwd, options.useWording);
25
+ if (options.scope)
26
+ return scope(cwd, options.scope, options.to, options.why);
25
27
  if (options.compile)
26
28
  return compile(cwd, options.json);
27
29
  if (options.approveCheck)
@@ -58,7 +60,7 @@ function list(cwd, json) {
58
60
  const label = { verified: chalk.green('lesson '), candidate: chalk.yellow('candidate'), rejected: chalk.red('rejected ') };
59
61
  for (const l of lessons) {
60
62
  const prs = [...new Set(l.evidence.map(e => `#${e.pr}`))].join(', ');
61
- const by = l.promotedBy ? chalk.dim(` [${l.promotedBy}]`) : '';
63
+ const by = chalk.dim(`${l.promotedBy ? ` [${l.promotedBy}]` : ''}${l.scope ? ` [${l.scope === 'repo' ? 'every change' : 'its folder'}]` : ''}`);
62
64
  console.log(`${label[l.state]} ${chalk.dim(l.id)} ${l.file || '(team standard)'}: ${l.text}${by} ${chalk.dim(`(${prs})`)}`);
63
65
  if (l.suggestedText)
64
66
  console.log(chalk.cyan(` corrected wording (${l.suggestedWhy ?? 'reworded'}): ${l.suggestedText}`) + chalk.dim(` take it: rigour learn-reviews --use-wording ${l.id}`));
@@ -117,6 +119,30 @@ function decideCheckOrThrow(cwd, id, state) {
117
119
  }
118
120
  console.log(state === 'active' ? chalk.green(`✔ Runs from now on (a note unless gates.compiled_lessons.block): ${check.message}`) : chalk.yellow(`✔ Taken back: ${check.message}`));
119
121
  }
122
+ /** A person sets how far a lesson reaches: its file, its folder, or every change (a repository standard). */
123
+ function scope(cwd, id, to, why) {
124
+ if (to !== 'file' && to !== 'folder' && to !== 'repo') {
125
+ console.error(chalk.red('--to is file, folder or repo.'));
126
+ process.exitCode = 1;
127
+ return;
128
+ }
129
+ let lesson;
130
+ try {
131
+ lesson = scopeLesson(cwd, id, to, personOf(cwd), why);
132
+ }
133
+ catch (e) {
134
+ console.error(chalk.red(e.message));
135
+ process.exitCode = 1;
136
+ return;
137
+ }
138
+ if (!lesson) {
139
+ console.error(chalk.red(`No lesson ${id}.`));
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ const reach = to === 'repo' ? 'every change, as a team standard' : to === 'folder' ? `every change in ${path.posix.dirname(lesson.file)}/` : `changes to ${lesson.file}`;
144
+ console.log(chalk.green(`✔ Reaches ${reach}${lesson.state === 'verified' ? '' : ' once it is a lesson (it is a candidate now)'}: ${lesson.text}`));
145
+ }
120
146
  /** A person takes the suggested wording of a lesson they decided; the old wording is kept as evidence. */
121
147
  function useWording(cwd, id) {
122
148
  const lesson = acceptSuggestedText(cwd, id, personOf(cwd));
@@ -43,6 +43,11 @@ export interface LessonJourney {
43
43
  text: string;
44
44
  why: string;
45
45
  };
46
+ /** How far a review lesson reaches (core scopeLesson): its file, its folder, or every change; `hasFile` false for a team standard. */
47
+ reach?: {
48
+ scope: 'file' | 'folder' | 'repo';
49
+ hasFile: boolean;
50
+ };
46
51
  }
47
52
  export interface StudioLearning {
48
53
  lessons: LessonJourney[];
@@ -7,7 +7,7 @@
7
7
  * lesson's subject prefix). Counts that Rigour cannot know here are null, never 0: PR catches
8
8
  * recorded on another machine (CI) never reach this one.
9
9
  */
10
- import { acceptSuggestedText, decideCompiledCheck, decideLesson, fixLessonPrefix, proposeCompiledChecks, readCompiledChecks, suspension, localOutcomeMetrics, pendingDecision, listKnowledgeLessons, readLessons } from '@rigour-labs/core';
10
+ import { acceptSuggestedText, scopeLesson, decideCompiledCheck, decideLesson, fixLessonPrefix, proposeCompiledChecks, readCompiledChecks, suspension, localOutcomeMetrics, pendingDecision, listKnowledgeLessons, readLessons } from '@rigour-labs/core';
11
11
  import { personOf } from './git-identity.js';
12
12
  import { checkoutRoots, eventsAcross, storiesAcross } from './studio-checkouts.js';
13
13
  const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
@@ -51,6 +51,7 @@ export function buildLearning(input) {
51
51
  reachedPr: null,
52
52
  ...decisionFor(l),
53
53
  ...(l.suggestedText ? { suggestedText: { text: l.suggestedText, why: l.suggestedWhy ?? 'reworded' } } : {}),
54
+ reach: { scope: l.scope ?? 'file', hasFile: !!l.file },
54
55
  })),
55
56
  // Lessons back to a candidate when outcomes stopped promoting come first: a person decides each once.
56
57
  ].sort((a, b) => Number(!!b.reclassified) - Number(!!a.reclassified) || b.learnedAt.localeCompare(a.learnedAt))
@@ -88,6 +89,10 @@ function decisionFor(lesson) {
88
89
  return { canDecide: true, suggested: { detail: last.detail ?? '', pr: last.pr, at: last.at ?? '' } };
89
90
  return { canDecide: false };
90
91
  }
92
+ /** The reason a person gave in Studio, else that they decided there. */
93
+ function studioWhy(why) {
94
+ return typeof why === 'string' && why.trim() ? why.trim() : 'decided in Studio';
95
+ }
91
96
  /** A person's decision on a review lesson from Studio, recorded as `rigour learn-reviews --promote / --reject` records it: with their git email, and final. */
92
97
  export function decideReviewLesson(cwd, body) {
93
98
  const { id, decision, why } = (body ?? {});
@@ -95,9 +100,26 @@ export function decideReviewLesson(cwd, body) {
95
100
  throw new Error('a review lesson id (12 hex characters) is required');
96
101
  if (decision === 'reworded')
97
102
  return rewordFromStudio(cwd, id);
103
+ if (decision === 'scope')
104
+ return scopeFromStudio(cwd, id, body.to, why);
98
105
  if (decision !== 'accepted' && decision !== 'rejected' && decision !== 'dismissed')
99
- throw new Error('decision is accepted, rejected, dismissed or reworded');
100
- const lesson = decideLesson(cwd, id, decision, personOf(cwd), typeof why === 'string' && why.trim() ? why.trim() : 'decided in Studio');
106
+ throw new Error('decision is accepted, rejected, dismissed, reworded or scope');
107
+ const lesson = decideLesson(cwd, id, decision, personOf(cwd), studioWhy(why));
108
+ if (!lesson)
109
+ throw new Error(`no review lesson ${id}`);
110
+ return { id: lesson.id, state: lesson.state };
111
+ }
112
+ /**
113
+ * A person sets how far a review lesson reaches from Studio, as `rigour learn-reviews --scope` records it: with their
114
+ * git email, so with no git email set it is refused, like a compiled check's decision.
115
+ */
116
+ function scopeFromStudio(cwd, id, to, why) {
117
+ if (to !== 'file' && to !== 'folder' && to !== 'repo')
118
+ throw new Error('to is file, folder or repo');
119
+ const by = personOf(cwd);
120
+ if (by === 'unknown')
121
+ throw new Error('no git email is set in this checkout (git config user.email): who decides how far a lesson reaches is recorded with it');
122
+ const lesson = scopeLesson(cwd, id, to, by, studioWhy(why));
101
123
  if (!lesson)
102
124
  throw new Error(`no review lesson ${id}`);
103
125
  return { id: lesson.id, state: lesson.state };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/cli",
3
- "version": "6.11.2",
3
+ "version": "6.12.0-rc.2",
4
4
  "description": "Code review that happens while your agent writes: deterministic checks on the lines it changed, quiet unless it can prove a defect, and learning from your team's fixes and dismissals. Works with Claude Code, Cursor, Codex, Cline and Windsurf.",
5
5
  "engines": {
6
6
  "node": ">=22.13"
@@ -59,13 +59,13 @@
59
59
  "inquirer": "9.2.16",
60
60
  "ora": "^8.0.1",
61
61
  "yaml": "^2.8.2",
62
- "@rigour-labs/core": "6.11.2"
62
+ "@rigour-labs/core": "6.12.0-rc.2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/fs-extra": "^11.0.4",
66
66
  "@types/inquirer": "9.0.7",
67
67
  "@types/node": "^25.0.3",
68
- "@rigour-labs/studio": "6.11.2"
68
+ "@rigour-labs/studio": "6.12.0-rc.2"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "tsc && pnpm bundle-studio",