@rigour-labs/core 6.8.1 → 6.9.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.
Files changed (40) hide show
  1. package/dist/goal/goal.d.ts +34 -0
  2. package/dist/goal/goal.js +188 -0
  3. package/dist/goal/hook.d.ts +7 -0
  4. package/dist/goal/hook.js +100 -0
  5. package/dist/hooks/stop-review.js +4 -1
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +4 -0
  8. package/dist/outcomes/outcome.d.ts +80 -0
  9. package/dist/outcomes/outcome.js +161 -0
  10. package/dist/outcomes/run.d.ts +26 -0
  11. package/dist/outcomes/run.js +63 -0
  12. package/dist/review/backtest-init.d.ts +9 -4
  13. package/dist/review/backtest-init.js +2 -2
  14. package/dist/review/quiet.js +2 -1
  15. package/dist/review/review.d.ts +5 -0
  16. package/dist/review/review.js +23 -2
  17. package/dist/review/reviewer/context.d.ts +11 -0
  18. package/dist/review/reviewer/context.js +10 -4
  19. package/dist/review/reviewer/prompt.d.ts +8 -1
  20. package/dist/review/reviewer/prompt.js +21 -2
  21. package/dist/review/reviewer/verdict.d.ts +12 -1
  22. package/dist/review/reviewer/verdict.js +8 -1
  23. package/dist/review/reviewer.d.ts +4 -0
  24. package/dist/review/reviewer.js +14 -6
  25. package/dist/review-learning/lessons.d.ts +11 -3
  26. package/dist/review-learning/lessons.js +3 -0
  27. package/dist/review-learning/outcome-evidence.d.ts +41 -0
  28. package/dist/review-learning/outcome-evidence.js +72 -0
  29. package/dist/review-learning/outcomes.d.ts +8 -0
  30. package/dist/review-learning/outcomes.js +11 -5
  31. package/dist/settings.d.ts +2 -0
  32. package/dist/switches.d.ts +640 -0
  33. package/dist/switches.js +36 -0
  34. package/dist/task/thread.d.ts +9 -1
  35. package/dist/task/thread.js +33 -1
  36. package/dist/templates/universal-config.js +4 -0
  37. package/dist/types/fix-packet.d.ts +1 -1
  38. package/dist/types/index.d.ts +56 -0
  39. package/dist/types/index.js +18 -0
  40. package/package.json +6 -6
@@ -0,0 +1,640 @@
1
+ /**
2
+ * A feature a team can turn off, on or require, and a person or a run can choose within that, as for the reviewer. Four
3
+ * layers, the nearest wins: a flag on this run, an environment variable (hooks and CI take no flags), the person's own
4
+ * settings (a key in the profile's settings.json), and the team's rigour.yml. `required` is the team's floor: a nearer
5
+ * layer that turns the feature off is refused, and the refusal is reported, never silent.
6
+ */
7
+ import { type RigourSettings } from './settings.js';
8
+ import type { Config } from './types/index.js';
9
+ import type { Source } from './review/reviewer/settings.js';
10
+ export type TeamSwitch = 'off' | 'on' | 'required';
11
+ export declare const SWITCHES: {
12
+ goal: {
13
+ label: string;
14
+ env: string;
15
+ teamPath: string[];
16
+ team: (config: {
17
+ ignore: string[];
18
+ commands: {
19
+ format?: string | undefined;
20
+ lint?: string | undefined;
21
+ typecheck?: string | undefined;
22
+ test?: string | undefined;
23
+ };
24
+ gates: {
25
+ deep: {
26
+ temperature: number;
27
+ intent_checks: boolean;
28
+ checks: {
29
+ architecture: boolean;
30
+ test_quality: boolean;
31
+ solid: boolean;
32
+ dry: boolean;
33
+ design_patterns: boolean;
34
+ language_idioms: boolean;
35
+ error_handling: boolean;
36
+ code_smells: boolean;
37
+ };
38
+ max_tokens?: number | undefined;
39
+ timeout_ms?: number | undefined;
40
+ budget_ms?: number | undefined;
41
+ agentic?: boolean | undefined;
42
+ repo_rules?: boolean | undefined;
43
+ review_lessons?: "verified" | "all" | "off" | undefined;
44
+ router?: {
45
+ enabled?: boolean | undefined;
46
+ min_score?: number | undefined;
47
+ max_functions?: number | undefined;
48
+ } | undefined;
49
+ };
50
+ max_file_lines: number;
51
+ forbid_todos: boolean;
52
+ forbid_fixme: boolean;
53
+ required_files: string[];
54
+ ast: {
55
+ complexity: number;
56
+ max_methods: number;
57
+ max_params: number;
58
+ };
59
+ staleness: {
60
+ enabled: boolean;
61
+ rules: Record<string, boolean>;
62
+ };
63
+ dependencies: {
64
+ forbid: string[];
65
+ detect_unused: boolean;
66
+ detect_heavy_alternatives: boolean;
67
+ detect_duplicate_purpose: boolean;
68
+ unused_allowlist: string[];
69
+ };
70
+ architecture: {
71
+ boundaries: {
72
+ from: string;
73
+ to: string;
74
+ mode: "allow" | "deny";
75
+ }[];
76
+ };
77
+ safety: {
78
+ max_files_changed_per_cycle: number;
79
+ protected_paths: string[];
80
+ };
81
+ context: {
82
+ enabled: boolean;
83
+ sensitivity: number;
84
+ mining_depth: number;
85
+ };
86
+ environment: {
87
+ enabled: boolean;
88
+ enforce_contracts: boolean;
89
+ tools: Record<string, string>;
90
+ required_env: string[];
91
+ };
92
+ retry_loop_breaker: {
93
+ enabled: boolean;
94
+ max_retries: number;
95
+ doc_sources: Record<string, string>;
96
+ };
97
+ agent_team: {
98
+ enabled: boolean;
99
+ max_concurrent_agents: number;
100
+ task_ownership: "strict" | "collaborative";
101
+ };
102
+ checkpoint: {
103
+ enabled: boolean;
104
+ interval_minutes: number;
105
+ quality_threshold: number;
106
+ drift_detection: boolean;
107
+ };
108
+ security: {
109
+ enabled: boolean;
110
+ sql_injection: boolean;
111
+ xss: boolean;
112
+ path_traversal: boolean;
113
+ hardcoded_secrets: boolean;
114
+ insecure_randomness: boolean;
115
+ command_injection: boolean;
116
+ block_on_severity: "critical" | "high" | "medium" | "low";
117
+ };
118
+ frontend_secret_exposure: {
119
+ enabled: boolean;
120
+ block_on_severity: "critical" | "high" | "medium" | "low";
121
+ check_process_env: boolean;
122
+ check_import_meta_env: boolean;
123
+ secret_env_name_patterns: string[];
124
+ safe_public_prefixes: string[];
125
+ frontend_path_patterns: string[];
126
+ server_path_patterns: string[];
127
+ allowlist_env_names: string[];
128
+ };
129
+ duplication_drift: {
130
+ enabled: boolean;
131
+ similarity_threshold: number;
132
+ min_body_lines: number;
133
+ };
134
+ hallucinated_imports: {
135
+ enabled: boolean;
136
+ ignore_patterns: string[];
137
+ };
138
+ inconsistent_error_handling: {
139
+ enabled: boolean;
140
+ max_strategies_per_type: number;
141
+ min_occurrences: number;
142
+ ignore_empty_catches: boolean;
143
+ };
144
+ context_window_artifacts: {
145
+ enabled: boolean;
146
+ min_file_lines: number;
147
+ degradation_threshold: number;
148
+ signals_required: number;
149
+ };
150
+ promise_safety: {
151
+ enabled: boolean;
152
+ ignore_patterns: string[];
153
+ check_unhandled_then: boolean;
154
+ check_unsafe_parse: boolean;
155
+ check_async_without_await: boolean;
156
+ check_unsafe_fetch: boolean;
157
+ };
158
+ phantom_apis: {
159
+ enabled: boolean;
160
+ ignore_patterns: string[];
161
+ check_node: boolean;
162
+ check_python: boolean;
163
+ check_go: boolean;
164
+ check_csharp: boolean;
165
+ check_java: boolean;
166
+ };
167
+ deprecated_apis: {
168
+ enabled: boolean;
169
+ ignore_patterns: string[];
170
+ check_node: boolean;
171
+ check_python: boolean;
172
+ check_go: boolean;
173
+ check_csharp: boolean;
174
+ check_java: boolean;
175
+ check_web: boolean;
176
+ block_security_deprecated: boolean;
177
+ };
178
+ test_quality: {
179
+ enabled: boolean;
180
+ check_empty_tests: boolean;
181
+ check_tautological: boolean;
182
+ check_mock_heavy: boolean;
183
+ check_assertion_free_async: boolean;
184
+ max_mocks_per_test: number;
185
+ };
186
+ governance: {
187
+ enabled: boolean;
188
+ enforce_memory: boolean;
189
+ enforce_skills: boolean;
190
+ block_native_memory: boolean;
191
+ protected_memory_paths: string[];
192
+ protected_skills_paths: string[];
193
+ exempt_paths: string[];
194
+ };
195
+ unindexed_reads: {
196
+ enabled: boolean;
197
+ migrations: string[];
198
+ };
199
+ deprecated_dependencies: {
200
+ enabled: boolean;
201
+ registry?: string | undefined;
202
+ };
203
+ unused_exports: {
204
+ enabled: boolean;
205
+ allow: string[];
206
+ block: boolean;
207
+ };
208
+ query_patterns: {
209
+ enabled: boolean;
210
+ };
211
+ change_sweep: {
212
+ enabled: boolean;
213
+ };
214
+ redundancy: {
215
+ enabled: boolean;
216
+ wire_contracts: string[];
217
+ schema_migrations: string[];
218
+ };
219
+ optional_params: {
220
+ enabled: boolean;
221
+ };
222
+ duplicate_functions: {
223
+ enabled: boolean;
224
+ };
225
+ orphan_files: {
226
+ enabled: boolean;
227
+ allow: string[];
228
+ block: boolean;
229
+ };
230
+ migration_order: {
231
+ enabled: boolean;
232
+ dirs: string[];
233
+ };
234
+ semantic_bugs: {
235
+ enabled: boolean;
236
+ rules?: string[] | undefined;
237
+ };
238
+ side_effect_analysis: {
239
+ enabled: boolean;
240
+ ignore_patterns: string[];
241
+ check_unbounded_timers: boolean;
242
+ check_unbounded_loops: boolean;
243
+ check_process_lifecycle: boolean;
244
+ check_recursive_depth: boolean;
245
+ check_resource_lifecycle: boolean;
246
+ check_retry_without_limit: boolean;
247
+ check_circular_triggers: boolean;
248
+ check_auto_restart: boolean;
249
+ };
250
+ style_drift: {
251
+ enabled: boolean;
252
+ deviation_threshold: number;
253
+ sample_size: number;
254
+ baseline_path: string;
255
+ };
256
+ logic_drift: {
257
+ enabled: boolean;
258
+ baseline_path: string;
259
+ track_operators: boolean;
260
+ track_branches: boolean;
261
+ track_returns: boolean;
262
+ };
263
+ };
264
+ hooks: {
265
+ require_review_ack: boolean;
266
+ };
267
+ output: {
268
+ report_path: string;
269
+ };
270
+ brief: {
271
+ enabled: boolean;
272
+ max_items: number;
273
+ };
274
+ learning: {
275
+ outcomes: {
276
+ mode: "off" | "on" | "required";
277
+ window_days: number;
278
+ demote_after: number;
279
+ };
280
+ };
281
+ review: {
282
+ include_heuristics: boolean;
283
+ show_preexisting: boolean;
284
+ goal: "off" | "on" | "required";
285
+ reviewer: {
286
+ enabled: boolean;
287
+ mode: "single" | "cross" | "full";
288
+ timeout_ms: number;
289
+ on_push: "off" | "background" | "wait";
290
+ reviewers: string[];
291
+ models: Record<string, string>;
292
+ panel: "off" | "on" | "required";
293
+ mode_required: boolean;
294
+ dismissals: boolean;
295
+ panel_max_items: number;
296
+ judges: 2 | 3;
297
+ escalate: "always" | "risk";
298
+ cross_models: Record<string, string>;
299
+ reasoning: Record<string, "high" | "medium" | "low">;
300
+ judge_env: Record<string, {
301
+ unset: string[];
302
+ }>;
303
+ model?: string | undefined;
304
+ max_runs_per_day?: number | undefined;
305
+ max_usd_per_day?: number | undefined;
306
+ api?: {
307
+ model: string;
308
+ url: string;
309
+ key_env: string;
310
+ max_turns: number;
311
+ vendor?: "anthropic" | "openai" | "google" | "other" | undefined;
312
+ } | undefined;
313
+ };
314
+ github_account?: string | undefined;
315
+ };
316
+ preset?: string | undefined;
317
+ paradigm?: string | undefined;
318
+ }) => "off" | "on" | "required";
319
+ };
320
+ outcomes: {
321
+ label: string;
322
+ env: string;
323
+ teamPath: string[];
324
+ team: (config: {
325
+ ignore: string[];
326
+ commands: {
327
+ format?: string | undefined;
328
+ lint?: string | undefined;
329
+ typecheck?: string | undefined;
330
+ test?: string | undefined;
331
+ };
332
+ gates: {
333
+ deep: {
334
+ temperature: number;
335
+ intent_checks: boolean;
336
+ checks: {
337
+ architecture: boolean;
338
+ test_quality: boolean;
339
+ solid: boolean;
340
+ dry: boolean;
341
+ design_patterns: boolean;
342
+ language_idioms: boolean;
343
+ error_handling: boolean;
344
+ code_smells: boolean;
345
+ };
346
+ max_tokens?: number | undefined;
347
+ timeout_ms?: number | undefined;
348
+ budget_ms?: number | undefined;
349
+ agentic?: boolean | undefined;
350
+ repo_rules?: boolean | undefined;
351
+ review_lessons?: "verified" | "all" | "off" | undefined;
352
+ router?: {
353
+ enabled?: boolean | undefined;
354
+ min_score?: number | undefined;
355
+ max_functions?: number | undefined;
356
+ } | undefined;
357
+ };
358
+ max_file_lines: number;
359
+ forbid_todos: boolean;
360
+ forbid_fixme: boolean;
361
+ required_files: string[];
362
+ ast: {
363
+ complexity: number;
364
+ max_methods: number;
365
+ max_params: number;
366
+ };
367
+ staleness: {
368
+ enabled: boolean;
369
+ rules: Record<string, boolean>;
370
+ };
371
+ dependencies: {
372
+ forbid: string[];
373
+ detect_unused: boolean;
374
+ detect_heavy_alternatives: boolean;
375
+ detect_duplicate_purpose: boolean;
376
+ unused_allowlist: string[];
377
+ };
378
+ architecture: {
379
+ boundaries: {
380
+ from: string;
381
+ to: string;
382
+ mode: "allow" | "deny";
383
+ }[];
384
+ };
385
+ safety: {
386
+ max_files_changed_per_cycle: number;
387
+ protected_paths: string[];
388
+ };
389
+ context: {
390
+ enabled: boolean;
391
+ sensitivity: number;
392
+ mining_depth: number;
393
+ };
394
+ environment: {
395
+ enabled: boolean;
396
+ enforce_contracts: boolean;
397
+ tools: Record<string, string>;
398
+ required_env: string[];
399
+ };
400
+ retry_loop_breaker: {
401
+ enabled: boolean;
402
+ max_retries: number;
403
+ doc_sources: Record<string, string>;
404
+ };
405
+ agent_team: {
406
+ enabled: boolean;
407
+ max_concurrent_agents: number;
408
+ task_ownership: "strict" | "collaborative";
409
+ };
410
+ checkpoint: {
411
+ enabled: boolean;
412
+ interval_minutes: number;
413
+ quality_threshold: number;
414
+ drift_detection: boolean;
415
+ };
416
+ security: {
417
+ enabled: boolean;
418
+ sql_injection: boolean;
419
+ xss: boolean;
420
+ path_traversal: boolean;
421
+ hardcoded_secrets: boolean;
422
+ insecure_randomness: boolean;
423
+ command_injection: boolean;
424
+ block_on_severity: "critical" | "high" | "medium" | "low";
425
+ };
426
+ frontend_secret_exposure: {
427
+ enabled: boolean;
428
+ block_on_severity: "critical" | "high" | "medium" | "low";
429
+ check_process_env: boolean;
430
+ check_import_meta_env: boolean;
431
+ secret_env_name_patterns: string[];
432
+ safe_public_prefixes: string[];
433
+ frontend_path_patterns: string[];
434
+ server_path_patterns: string[];
435
+ allowlist_env_names: string[];
436
+ };
437
+ duplication_drift: {
438
+ enabled: boolean;
439
+ similarity_threshold: number;
440
+ min_body_lines: number;
441
+ };
442
+ hallucinated_imports: {
443
+ enabled: boolean;
444
+ ignore_patterns: string[];
445
+ };
446
+ inconsistent_error_handling: {
447
+ enabled: boolean;
448
+ max_strategies_per_type: number;
449
+ min_occurrences: number;
450
+ ignore_empty_catches: boolean;
451
+ };
452
+ context_window_artifacts: {
453
+ enabled: boolean;
454
+ min_file_lines: number;
455
+ degradation_threshold: number;
456
+ signals_required: number;
457
+ };
458
+ promise_safety: {
459
+ enabled: boolean;
460
+ ignore_patterns: string[];
461
+ check_unhandled_then: boolean;
462
+ check_unsafe_parse: boolean;
463
+ check_async_without_await: boolean;
464
+ check_unsafe_fetch: boolean;
465
+ };
466
+ phantom_apis: {
467
+ enabled: boolean;
468
+ ignore_patterns: string[];
469
+ check_node: boolean;
470
+ check_python: boolean;
471
+ check_go: boolean;
472
+ check_csharp: boolean;
473
+ check_java: boolean;
474
+ };
475
+ deprecated_apis: {
476
+ enabled: boolean;
477
+ ignore_patterns: string[];
478
+ check_node: boolean;
479
+ check_python: boolean;
480
+ check_go: boolean;
481
+ check_csharp: boolean;
482
+ check_java: boolean;
483
+ check_web: boolean;
484
+ block_security_deprecated: boolean;
485
+ };
486
+ test_quality: {
487
+ enabled: boolean;
488
+ check_empty_tests: boolean;
489
+ check_tautological: boolean;
490
+ check_mock_heavy: boolean;
491
+ check_assertion_free_async: boolean;
492
+ max_mocks_per_test: number;
493
+ };
494
+ governance: {
495
+ enabled: boolean;
496
+ enforce_memory: boolean;
497
+ enforce_skills: boolean;
498
+ block_native_memory: boolean;
499
+ protected_memory_paths: string[];
500
+ protected_skills_paths: string[];
501
+ exempt_paths: string[];
502
+ };
503
+ unindexed_reads: {
504
+ enabled: boolean;
505
+ migrations: string[];
506
+ };
507
+ deprecated_dependencies: {
508
+ enabled: boolean;
509
+ registry?: string | undefined;
510
+ };
511
+ unused_exports: {
512
+ enabled: boolean;
513
+ allow: string[];
514
+ block: boolean;
515
+ };
516
+ query_patterns: {
517
+ enabled: boolean;
518
+ };
519
+ change_sweep: {
520
+ enabled: boolean;
521
+ };
522
+ redundancy: {
523
+ enabled: boolean;
524
+ wire_contracts: string[];
525
+ schema_migrations: string[];
526
+ };
527
+ optional_params: {
528
+ enabled: boolean;
529
+ };
530
+ duplicate_functions: {
531
+ enabled: boolean;
532
+ };
533
+ orphan_files: {
534
+ enabled: boolean;
535
+ allow: string[];
536
+ block: boolean;
537
+ };
538
+ migration_order: {
539
+ enabled: boolean;
540
+ dirs: string[];
541
+ };
542
+ semantic_bugs: {
543
+ enabled: boolean;
544
+ rules?: string[] | undefined;
545
+ };
546
+ side_effect_analysis: {
547
+ enabled: boolean;
548
+ ignore_patterns: string[];
549
+ check_unbounded_timers: boolean;
550
+ check_unbounded_loops: boolean;
551
+ check_process_lifecycle: boolean;
552
+ check_recursive_depth: boolean;
553
+ check_resource_lifecycle: boolean;
554
+ check_retry_without_limit: boolean;
555
+ check_circular_triggers: boolean;
556
+ check_auto_restart: boolean;
557
+ };
558
+ style_drift: {
559
+ enabled: boolean;
560
+ deviation_threshold: number;
561
+ sample_size: number;
562
+ baseline_path: string;
563
+ };
564
+ logic_drift: {
565
+ enabled: boolean;
566
+ baseline_path: string;
567
+ track_operators: boolean;
568
+ track_branches: boolean;
569
+ track_returns: boolean;
570
+ };
571
+ };
572
+ hooks: {
573
+ require_review_ack: boolean;
574
+ };
575
+ output: {
576
+ report_path: string;
577
+ };
578
+ brief: {
579
+ enabled: boolean;
580
+ max_items: number;
581
+ };
582
+ learning: {
583
+ outcomes: {
584
+ mode: "off" | "on" | "required";
585
+ window_days: number;
586
+ demote_after: number;
587
+ };
588
+ };
589
+ review: {
590
+ include_heuristics: boolean;
591
+ show_preexisting: boolean;
592
+ goal: "off" | "on" | "required";
593
+ reviewer: {
594
+ enabled: boolean;
595
+ mode: "single" | "cross" | "full";
596
+ timeout_ms: number;
597
+ on_push: "off" | "background" | "wait";
598
+ reviewers: string[];
599
+ models: Record<string, string>;
600
+ panel: "off" | "on" | "required";
601
+ mode_required: boolean;
602
+ dismissals: boolean;
603
+ panel_max_items: number;
604
+ judges: 2 | 3;
605
+ escalate: "always" | "risk";
606
+ cross_models: Record<string, string>;
607
+ reasoning: Record<string, "high" | "medium" | "low">;
608
+ judge_env: Record<string, {
609
+ unset: string[];
610
+ }>;
611
+ model?: string | undefined;
612
+ max_runs_per_day?: number | undefined;
613
+ max_usd_per_day?: number | undefined;
614
+ api?: {
615
+ model: string;
616
+ url: string;
617
+ key_env: string;
618
+ max_turns: number;
619
+ vendor?: "anthropic" | "openai" | "google" | "other" | undefined;
620
+ } | undefined;
621
+ };
622
+ github_account?: string | undefined;
623
+ };
624
+ preset?: string | undefined;
625
+ paradigm?: string | undefined;
626
+ }) => "off" | "on" | "required";
627
+ };
628
+ };
629
+ export type SwitchName = keyof typeof SWITCHES & keyof RigourSettings;
630
+ export interface ResolvedSwitch {
631
+ enabled: boolean;
632
+ source: Source;
633
+ /** The team requires it: no nearer layer turns it off. */
634
+ required: boolean;
635
+ /** Nearer choices that were not applied, one line each, for the report. */
636
+ refused: string[];
637
+ }
638
+ export declare function resolveSwitch(name: SwitchName, config: Config, flag?: boolean, user?: boolean | undefined, env?: NodeJS.ProcessEnv): ResolvedSwitch;
639
+ /** The person's own choice, from Studio: true or false sets it, `null` goes back to the team's. Returns what is stored. */
640
+ export declare function saveUserSwitch(name: SwitchName, value: unknown): boolean | undefined;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A feature a team can turn off, on or require, and a person or a run can choose within that, as for the reviewer. Four
3
+ * layers, the nearest wins: a flag on this run, an environment variable (hooks and CI take no flags), the person's own
4
+ * settings (a key in the profile's settings.json), and the team's rigour.yml. `required` is the team's floor: a nearer
5
+ * layer that turns the feature off is refused, and the refusal is reported, never silent.
6
+ */
7
+ import { loadSettings, saveSettings } from './settings.js';
8
+ export const SWITCHES = {
9
+ goal: { label: 'goal check', env: 'RIGOUR_GOAL', teamPath: ['review', 'goal'], team: config => config.review?.goal },
10
+ outcomes: { label: 'outcome loop', env: 'RIGOUR_OUTCOMES', teamPath: ['learning', 'outcomes', 'mode'], team: config => config.learning?.outcomes?.mode },
11
+ };
12
+ export function resolveSwitch(name, config, flag, user = loadSettings()[name], env = process.env) {
13
+ const definition = SWITCHES[name];
14
+ const team = definition.team(config) ?? 'off';
15
+ const required = team === 'required';
16
+ const refused = [];
17
+ const given = env[definition.env];
18
+ const raw = given?.trim().toLowerCase();
19
+ const fromEnv = raw === 'on' || raw === '1' || raw === 'true' ? true : raw === 'off' || raw === '0' || raw === 'false' ? false : undefined;
20
+ if (raw && fromEnv === undefined)
21
+ refused.push(`${definition.env}=${given} ignored: use on or off`);
22
+ const near = [['flag', flag], ['env', fromEnv], ['user', user]].find(([, value]) => value !== undefined);
23
+ if (near && !(required && near[1] === false))
24
+ return { enabled: near[1], source: near[0], required, refused };
25
+ if (near)
26
+ refused.push(`${definition.label} off (${near[0]}) refused: rigour.yml sets ${definition.teamPath.join('.')}: required`);
27
+ return { enabled: team !== 'off', source: 'team', required, refused };
28
+ }
29
+ /** The person's own choice, from Studio: true or false sets it, `null` goes back to the team's. Returns what is stored. */
30
+ export function saveUserSwitch(name, value) {
31
+ if (value !== null && typeof value !== 'boolean')
32
+ throw new Error(`${name} is true, false, or null for the team's`);
33
+ const { [name]: _previous, ...settings } = loadSettings();
34
+ saveSettings(value === null ? settings : { ...settings, [name]: value });
35
+ return value ?? undefined;
36
+ }