@rigour-labs/core 6.4.2 → 6.4.4

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.
@@ -21,5 +21,9 @@ export interface CodePassConfig extends PassConfig {
21
21
  }
22
22
  export interface CodePassResult extends PassResult {
23
23
  contexts: CodeContext[];
24
+ /** Findings the model proposed, before the self-check (the max and cloud tiers). */
25
+ proposed: number;
26
+ /** Of those, withdrawn by the self-check. */
27
+ withdrawn: number;
24
28
  }
25
29
  export declare function runCodePass(provider: InferenceProvider, facts: FileFacts[], config: CodePassConfig): Promise<CodePassResult>;
@@ -11,7 +11,7 @@ import { buildReferencePack } from './reference-pack.js';
11
11
  import { selfCheck } from './self-check.js';
12
12
  import { Logger } from '../utils/logger.js';
13
13
  export async function runCodePass(provider, facts, config) {
14
- const result = { findings: [], chunksTotal: facts.length, chunksFailed: 0, contexts: [] };
14
+ const result = { findings: [], chunksTotal: facts.length, chunksFailed: 0, contexts: [], proposed: 0, withdrawn: 0 };
15
15
  for (let i = 0; i < facts.length; i++) {
16
16
  const file = facts[i];
17
17
  config.onProgress?.(` Reviewing ${file.path} (${i + 1}/${facts.length})...`);
@@ -32,7 +32,17 @@ export async function runCodePass(provider, facts, config) {
32
32
  context = { ...context, source: `${context.source}\n${pack.source}` };
33
33
  result.contexts.push(context);
34
34
  try {
35
- result.findings.push(...(pack ? await reviewTwiceAndCheck(provider, context, pack, config) : await reviewOnce(provider, context, '', config)));
35
+ if (pack) {
36
+ const checked = await reviewTwiceAndCheck(provider, context, pack, config);
37
+ result.findings.push(...checked.kept);
38
+ result.proposed += checked.proposed;
39
+ result.withdrawn += checked.withdrawn;
40
+ }
41
+ else {
42
+ const found = await reviewOnce(provider, context, '', config);
43
+ result.findings.push(...found);
44
+ result.proposed += found.length;
45
+ }
36
46
  }
37
47
  catch (error) {
38
48
  recordFailure(result, `${file.path}: ${error.message}`, config);
@@ -48,11 +58,11 @@ async function reviewTwiceAndCheck(provider, context, pack, config) {
48
58
  const second = pack.sections.length > 1 ? await reviewOnce(provider, context, [...pack.sections].reverse().join('\n\n'), config) : [];
49
59
  const union = dedupe([...first, ...second]);
50
60
  if (union.length === 0)
51
- return union;
61
+ return { kept: union, proposed: 0, withdrawn: 0 };
52
62
  const checked = await selfCheck(provider, context, pack.text, union, config.inference);
53
63
  if (checked.withdrawn > 0)
54
64
  config.onProgress?.(` Self-check withdrew ${checked.withdrawn} of ${union.length} finding(s) in ${context.file}.`);
55
- return checked.kept;
65
+ return { kept: checked.kept, proposed: union.length, withdrawn: checked.withdrawn };
56
66
  }
57
67
  function packFor(file, config) {
58
68
  const reference = config.reference;
@@ -39,6 +39,7 @@ describe('runCodePass', () => {
39
39
  const result = await run(provider);
40
40
  expect(prompts).toHaveLength(1);
41
41
  expect(result.findings.map(f => f.line)).toEqual([3]);
42
+ expect([result.proposed, result.withdrawn]).toEqual([1, 0]);
42
43
  });
43
44
  it('does not repeat an identical pass when there is only one reference section', async () => {
44
45
  const { provider, prompts } = fakeProvider([[]], []);
@@ -60,6 +61,7 @@ describe('runCodePass', () => {
60
61
  expect(reviews[1].indexOf('REMOVED by this change')).toBeLessThan(reviews[1].indexOf('PR DESCRIPTION'));
61
62
  expect(prompts.filter(p => p.startsWith('You reported a defect'))).toHaveLength(2);
62
63
  expect(result.findings.map(f => [f.line, f.category])).toEqual([[3, 'correctness']]);
64
+ expect([result.proposed, result.withdrawn]).toEqual([2, 1]); // two distinct findings, one withdrawn
63
65
  });
64
66
  it('reads a self-check reply only when it is a verdict', () => {
65
67
  expect(parseVerdict('{"real": false, "reason": "handled"}')).toBe(false);
@@ -31,6 +31,9 @@ export interface DeepRunOutcome {
31
31
  filesAnalyzed: number;
32
32
  chunksTotal: number;
33
33
  chunksFailed: number;
34
+ /** Code mode: findings the model proposed, and how many the self-check withdrew. */
35
+ findingsProposed?: number;
36
+ findingsWithdrawn?: number;
34
37
  /** Model actually used (the stock fallback is named as such). */
35
38
  model?: string;
36
39
  modelFallback?: boolean;
@@ -111,6 +111,8 @@ export class DeepAnalysisGate extends Gate {
111
111
  onProgress: this.config.onProgress,
112
112
  });
113
113
  this.recordPass(result);
114
+ this.outcome.findingsProposed = result.proposed;
115
+ this.outcome.findingsWithdrawn = result.withdrawn;
114
116
  return verifyCodeFindings(result.findings, result.contexts);
115
117
  }
116
118
  /** The max and cloud tiers read reference material and review twice; the small models do not. */
@@ -46,6 +46,8 @@ export async function runDeepAnalysis(config, context, deepOptions) {
46
46
  chunks_total: outcome.chunksTotal,
47
47
  chunks_failed: outcome.chunksFailed,
48
48
  error: outcome.error,
49
+ findings_proposed: outcome.findingsProposed,
50
+ findings_withdrawn: outcome.findingsWithdrawn,
49
51
  findings_count: findings.length,
50
52
  findings_verified: findings.filter(f => f.verified).length,
51
53
  };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,7 @@ export * from './context/index-bridge.js';
52
52
  export * from './context/automatic-index.js';
53
53
  export * from './context/dependency-graph.js';
54
54
  export * from './services/context-telemetry-service.js';
55
+ export * from './services/observed-savings.js';
55
56
  export * from './services/system-health.js';
56
57
  export * from './services/agent-history.js';
57
58
  export * from './services/engineering-knowledge-graph.js';
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ export * from './context/index-bridge.js';
51
51
  export * from './context/automatic-index.js';
52
52
  export * from './context/dependency-graph.js';
53
53
  export * from './services/context-telemetry-service.js';
54
+ export * from './services/observed-savings.js';
54
55
  export * from './services/system-health.js';
55
56
  export * from './services/agent-history.js';
56
57
  export * from './services/engineering-knowledge-graph.js';
@@ -3,6 +3,7 @@
3
3
  * Calculates exact observed usage, avoided tokens, cache performance, and cost breakdowns.
4
4
  * Supports CSV/JSON Cursor usage imports.
5
5
  */
6
+ import { type ObservedSavings } from './observed-savings.js';
6
7
  import { type CursorUsageSyncOptions } from './cursor-usage-client.js';
7
8
  /** Heuristic token counter (~4 chars per token for code/text) */
8
9
  export declare function estimateTokenCount(text: string): number;
@@ -57,6 +58,14 @@ export interface TaskCostStats {
57
58
  pricingSource: string;
58
59
  pricingEffectiveDate: string;
59
60
  };
61
+ /**
62
+ * Measured, not modelled: scoped files the agent's hook saw it never read,
63
+ * less the summaries it read instead. Only scopes followed by observed agent
64
+ * activity count (trackedScopes); the estimate above is an upper bound.
65
+ */
66
+ observed: ObservedSavings & {
67
+ avoidedCostUsd: number;
68
+ };
60
69
  }
61
70
  export interface CachePerformanceStats {
62
71
  exactCacheHits: number;
@@ -4,6 +4,7 @@
4
4
  * Supports CSV/JSON Cursor usage imports.
5
5
  */
6
6
  import { createHash } from 'crypto';
7
+ import { observedSavings } from './observed-savings.js';
7
8
  import fs from 'fs-extra';
8
9
  import path from 'path';
9
10
  import { getContextEvents, getModelUsages, getCheckpointMetrics, recordModelUsage, } from '../storage/index.js';
@@ -110,6 +111,8 @@ export async function getTaskCostStats(taskId, cwd) {
110
111
  actualPricingBasis = 'observed-cursor-charged-cents';
111
112
  }
112
113
  const retrievalPricing = estimateAvoidedContextCostUsd(stats.potentialAvoidedTokens, usages);
114
+ const observed = observedSavings(cwd ?? process.cwd());
115
+ const observedPricing = estimateAvoidedContextCostUsd(observed.avoidedTokens, usages);
113
116
  const checkpointPricing = estimateAvoidedContextCostUsd(stats.checkpointReplayAvoided, usages);
114
117
  const unknownPricing = retrievalPricing.pricing.pricingBasis.includes('fallback');
115
118
  const range = (tokens) => ({
@@ -150,6 +153,7 @@ export async function getTaskCostStats(taskId, cwd) {
150
153
  pricingSource: MODEL_PRICING_SOURCE,
151
154
  pricingEffectiveDate: MODEL_PRICING_EFFECTIVE_DATE,
152
155
  },
156
+ observed: { ...observed, avoidedCostUsd: observedPricing.costUsd },
153
157
  };
154
158
  }
155
159
  /**
@@ -0,0 +1,36 @@
1
+ export declare const OBSERVATION_WINDOW_MS: number;
2
+ export interface ScopeOffer {
3
+ ts: number;
4
+ files: Array<{
5
+ path: string;
6
+ tokens: number;
7
+ }>;
8
+ summaryTokens: number;
9
+ }
10
+ export interface AgentActivity {
11
+ ts: number;
12
+ tool: string;
13
+ path?: string;
14
+ }
15
+ export interface ObservedSavings {
16
+ scopes: number;
17
+ /** Scopes followed by agent activity the hook saw: the only ones measured. */
18
+ trackedScopes: number;
19
+ offeredTokens: number;
20
+ /** Tokens of scoped files the agent read anyway. */
21
+ readBackTokens: number;
22
+ /** Tokens of scoped files never read, less the summaries, over tracked scopes. */
23
+ avoidedTokens: number;
24
+ }
25
+ export declare function recordScopeOffer(cwd: string, files: Array<{
26
+ path: string;
27
+ tokens: number;
28
+ }>, summaryTokens: number, ts?: number): void;
29
+ /** One tool call an agent hook saw. Read-like tools carry the file they read. */
30
+ export declare function recordAgentActivity(cwd: string, tool: string, filePath?: string, ts?: number): void;
31
+ /**
32
+ * A hook payload for one agent tool call (Claude Code PreToolUse sends tool_name,
33
+ * tool_input and cwd); anything else is ignored.
34
+ */
35
+ export declare function recordHookPayload(payload: unknown, fallbackCwd: string, ts?: number): void;
36
+ export declare function observedSavings(cwd: string, windowMs?: number): ObservedSavings;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Observed context savings: tokens an agent did not read because Rigour scoped its context.
3
+ *
4
+ * The estimate in context telemetry assumes the agent would otherwise read every
5
+ * scoped file whole, and does not read them afterwards anyway. Neither is
6
+ * measured. Here both sides are observed:
7
+ *
8
+ * .rigour/context-scopes.jsonl files rigour_context_scope summarised, with their tokens
9
+ * .rigour/agent-activity.jsonl every tool call the agent's hook saw (and the file it read)
10
+ *
11
+ * A scope is tracked when the hook recorded any agent activity after it, so
12
+ * "the agent read nothing" is told apart from "no hook is installed". For a
13
+ * tracked scope, the saving is the tokens of scoped files the agent did not
14
+ * read within the window, less the summary it read instead.
15
+ */
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ const SCOPES = 'context-scopes.jsonl';
19
+ const ACTIVITY = 'agent-activity.jsonl';
20
+ const MAX_LOG_BYTES = 2 * 1024 * 1024;
21
+ export const OBSERVATION_WINDOW_MS = 30 * 60 * 1000;
22
+ const READ_TOOLS = /^(?:read|read_file|readfile|view|open_file|cat)$/i;
23
+ export function recordScopeOffer(cwd, files, summaryTokens, ts = Date.now()) {
24
+ append(cwd, SCOPES, { ts, files: files.map(f => ({ path: normalize(cwd, f.path), tokens: f.tokens })), summaryTokens });
25
+ }
26
+ /** One tool call an agent hook saw. Read-like tools carry the file they read. */
27
+ export function recordAgentActivity(cwd, tool, filePath, ts = Date.now()) {
28
+ const entry = { ts, tool };
29
+ if (filePath && READ_TOOLS.test(tool))
30
+ entry.path = normalize(cwd, filePath);
31
+ append(cwd, ACTIVITY, entry);
32
+ }
33
+ /**
34
+ * A hook payload for one agent tool call (Claude Code PreToolUse sends tool_name,
35
+ * tool_input and cwd); anything else is ignored.
36
+ */
37
+ export function recordHookPayload(payload, fallbackCwd, ts = Date.now()) {
38
+ const event = payload;
39
+ if (!event || typeof event.tool_name !== 'string')
40
+ return;
41
+ const input = event.tool_input ?? {};
42
+ const file = [input.file_path, input.path, input.target_file].find((v) => typeof v === 'string');
43
+ recordAgentActivity(typeof event.cwd === 'string' ? event.cwd : fallbackCwd, event.tool_name, file, ts);
44
+ }
45
+ export function observedSavings(cwd, windowMs = OBSERVATION_WINDOW_MS) {
46
+ const scopes = readLog(cwd, SCOPES);
47
+ const activity = readLog(cwd, ACTIVITY).sort((a, b) => a.ts - b.ts);
48
+ const result = { scopes: scopes.length, trackedScopes: 0, offeredTokens: 0, readBackTokens: 0, avoidedTokens: 0 };
49
+ for (const scope of scopes) {
50
+ const after = activity.filter(a => a.ts >= scope.ts && a.ts <= scope.ts + windowMs);
51
+ if (after.length === 0)
52
+ continue;
53
+ result.trackedScopes++;
54
+ const read = new Set(after.filter(a => a.path).map(a => a.path));
55
+ const offered = scope.files.reduce((sum, f) => sum + f.tokens, 0);
56
+ const readBack = scope.files.filter(f => read.has(f.path)).reduce((sum, f) => sum + f.tokens, 0);
57
+ result.offeredTokens += offered;
58
+ result.readBackTokens += readBack;
59
+ result.avoidedTokens += Math.max(0, offered - readBack - scope.summaryTokens);
60
+ }
61
+ return result;
62
+ }
63
+ function normalize(cwd, file) {
64
+ return path.relative(cwd, path.resolve(cwd, file)).split(path.sep).join('/');
65
+ }
66
+ function append(cwd, name, entry) {
67
+ try {
68
+ const dir = path.join(cwd, '.rigour');
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ const file = path.join(dir, name);
71
+ fs.appendFileSync(file, JSON.stringify(entry) + '\n');
72
+ if (fs.statSync(file).size > MAX_LOG_BYTES) {
73
+ const lines = fs.readFileSync(file, 'utf8').trimEnd().split('\n');
74
+ fs.writeFileSync(file, lines.slice(Math.floor(lines.length / 2)).join('\n') + '\n');
75
+ }
76
+ }
77
+ catch {
78
+ // Measurement must never break the agent's tool call.
79
+ }
80
+ }
81
+ function readLog(cwd, name) {
82
+ try {
83
+ return fs.readFileSync(path.join(cwd, '.rigour', name), 'utf8').split('\n').filter(Boolean)
84
+ .flatMap(line => { try {
85
+ return [JSON.parse(line)];
86
+ }
87
+ catch {
88
+ return [];
89
+ } });
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { observedSavings, recordAgentActivity, recordHookPayload, recordScopeOffer } from './observed-savings.js';
6
+ let cwd;
7
+ beforeEach(() => { cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'observed-savings-')); });
8
+ afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
9
+ const T = 1_000_000;
10
+ describe('observedSavings', () => {
11
+ it('counts only scoped files the agent did not read afterwards, less the summary', () => {
12
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }, { path: 'src/b.ts', tokens: 2000 }], 400, T);
13
+ recordAgentActivity(cwd, 'Read', path.join(cwd, 'src/a.ts'), T + 60_000); // read anyway
14
+ recordAgentActivity(cwd, 'Edit', path.join(cwd, 'src/b.ts'), T + 90_000); // an edit is not a read
15
+ expect(observedSavings(cwd)).toEqual({ scopes: 1, trackedScopes: 1, offeredTokens: 5000, readBackTokens: 3000, avoidedTokens: 1600 });
16
+ });
17
+ it('claims nothing for a scope no hook observed, so a missing hook never reads as a saving', () => {
18
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
19
+ expect(observedSavings(cwd)).toMatchObject({ scopes: 1, trackedScopes: 0, avoidedTokens: 0 });
20
+ });
21
+ it('ignores activity outside the observation window', () => {
22
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
23
+ recordAgentActivity(cwd, 'Read', 'src/a.ts', T + 2 * 60 * 60 * 1000);
24
+ expect(observedSavings(cwd).trackedScopes).toBe(0);
25
+ });
26
+ it('never claims more than was offered when the summary outweighs unread files', () => {
27
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 100 }], 400, T);
28
+ recordAgentActivity(cwd, 'Grep', undefined, T + 1000);
29
+ expect(observedSavings(cwd).avoidedTokens).toBe(0);
30
+ });
31
+ it('reads a Claude Code PreToolUse payload: the tool, the file it reads, and its working directory', () => {
32
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
33
+ recordHookPayload({ tool_name: 'Read', tool_input: { file_path: path.join(cwd, 'src/a.ts') }, cwd }, '/elsewhere', T + 1000);
34
+ recordHookPayload({ hello: 'not a tool call' }, cwd, T + 2000);
35
+ expect(observedSavings(cwd)).toMatchObject({ trackedScopes: 1, readBackTokens: 3000, avoidedTokens: 0 });
36
+ });
37
+ });
@@ -3023,6 +3023,8 @@ export declare const ReportSchema: z.ZodObject<{
3023
3023
  chunks_total: z.ZodOptional<z.ZodNumber>;
3024
3024
  chunks_failed: z.ZodOptional<z.ZodNumber>;
3025
3025
  error: z.ZodOptional<z.ZodString>;
3026
+ findings_proposed: z.ZodOptional<z.ZodNumber>;
3027
+ findings_withdrawn: z.ZodOptional<z.ZodNumber>;
3026
3028
  findings_count: z.ZodOptional<z.ZodNumber>;
3027
3029
  findings_verified: z.ZodOptional<z.ZodNumber>;
3028
3030
  }, "strip", z.ZodTypeAny, {
@@ -3037,6 +3039,8 @@ export declare const ReportSchema: z.ZodObject<{
3037
3039
  files_analyzed?: number | undefined;
3038
3040
  chunks_total?: number | undefined;
3039
3041
  chunks_failed?: number | undefined;
3042
+ findings_proposed?: number | undefined;
3043
+ findings_withdrawn?: number | undefined;
3040
3044
  findings_count?: number | undefined;
3041
3045
  findings_verified?: number | undefined;
3042
3046
  }, {
@@ -3051,6 +3055,8 @@ export declare const ReportSchema: z.ZodObject<{
3051
3055
  files_analyzed?: number | undefined;
3052
3056
  chunks_total?: number | undefined;
3053
3057
  chunks_failed?: number | undefined;
3058
+ findings_proposed?: number | undefined;
3059
+ findings_withdrawn?: number | undefined;
3054
3060
  findings_count?: number | undefined;
3055
3061
  findings_verified?: number | undefined;
3056
3062
  }>>;
@@ -3068,6 +3074,8 @@ export declare const ReportSchema: z.ZodObject<{
3068
3074
  files_analyzed?: number | undefined;
3069
3075
  chunks_total?: number | undefined;
3070
3076
  chunks_failed?: number | undefined;
3077
+ findings_proposed?: number | undefined;
3078
+ findings_withdrawn?: number | undefined;
3071
3079
  findings_count?: number | undefined;
3072
3080
  findings_verified?: number | undefined;
3073
3081
  } | undefined;
@@ -3097,6 +3105,8 @@ export declare const ReportSchema: z.ZodObject<{
3097
3105
  files_analyzed?: number | undefined;
3098
3106
  chunks_total?: number | undefined;
3099
3107
  chunks_failed?: number | undefined;
3108
+ findings_proposed?: number | undefined;
3109
+ findings_withdrawn?: number | undefined;
3100
3110
  findings_count?: number | undefined;
3101
3111
  findings_verified?: number | undefined;
3102
3112
  } | undefined;
@@ -3128,6 +3138,8 @@ export declare const ReportSchema: z.ZodObject<{
3128
3138
  files_analyzed?: number | undefined;
3129
3139
  chunks_total?: number | undefined;
3130
3140
  chunks_failed?: number | undefined;
3141
+ findings_proposed?: number | undefined;
3142
+ findings_withdrawn?: number | undefined;
3131
3143
  findings_count?: number | undefined;
3132
3144
  findings_verified?: number | undefined;
3133
3145
  } | undefined;
@@ -3176,6 +3188,8 @@ export declare const ReportSchema: z.ZodObject<{
3176
3188
  files_analyzed?: number | undefined;
3177
3189
  chunks_total?: number | undefined;
3178
3190
  chunks_failed?: number | undefined;
3191
+ findings_proposed?: number | undefined;
3192
+ findings_withdrawn?: number | undefined;
3179
3193
  findings_count?: number | undefined;
3180
3194
  findings_verified?: number | undefined;
3181
3195
  } | undefined;
@@ -422,6 +422,8 @@ export const ReportSchema = z.object({
422
422
  chunks_total: z.number().optional(),
423
423
  chunks_failed: z.number().optional(),
424
424
  error: z.string().optional(),
425
+ findings_proposed: z.number().optional(), // code mode, before the self-check
426
+ findings_withdrawn: z.number().optional(), // by the self-check
425
427
  findings_count: z.number().optional(),
426
428
  findings_verified: z.number().optional(),
427
429
  }).optional(),
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.4.2",
3
+ "version": "6.4.4",
4
4
  "description": "AI-native quality gate engine with local Bayesian learning. AST analysis, drift detection, Fix Packet generation, and agent self-healing across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
5
8
  "license": "MIT",
6
9
  "homepage": "https://rigour.run",
7
10
  "keywords": [
@@ -67,11 +70,11 @@
67
70
  "pg": "^8.16.3",
68
71
  "sqlite3": "^5.1.7",
69
72
  "openai": "^4.104.0",
70
- "@rigour-labs/brain-darwin-arm64": "6.4.2",
71
- "@rigour-labs/brain-darwin-x64": "6.4.2",
72
- "@rigour-labs/brain-linux-arm64": "6.4.2",
73
- "@rigour-labs/brain-win-x64": "6.4.2",
74
- "@rigour-labs/brain-linux-x64": "6.4.2"
73
+ "@rigour-labs/brain-darwin-arm64": "6.4.4",
74
+ "@rigour-labs/brain-linux-arm64": "6.4.4",
75
+ "@rigour-labs/brain-win-x64": "6.4.4",
76
+ "@rigour-labs/brain-linux-x64": "6.4.4",
77
+ "@rigour-labs/brain-darwin-x64": "6.4.4"
75
78
  },
76
79
  "devDependencies": {
77
80
  "@types/fs-extra": "^11.0.4",