@rigour-labs/core 3.0.0 → 3.0.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.
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Hook configuration templates for each AI coding tool.
3
+ *
4
+ * Each template generates the tool-native config format:
5
+ * - Claude Code: .claude/settings.json (PostToolUse matcher)
6
+ * - Cursor: .cursor/hooks.json (afterFileEdit event)
7
+ * - Cline: .clinerules/hooks/PostToolUse (executable script)
8
+ * - Windsurf: .windsurf/hooks.json (post_write_code event)
9
+ *
10
+ * @since v3.0.0
11
+ */
12
+ /**
13
+ * Generate hook config files for a specific tool.
14
+ */
15
+ export function generateHookFiles(tool, checkerPath) {
16
+ switch (tool) {
17
+ case 'claude':
18
+ return generateClaudeHooks(checkerPath);
19
+ case 'cursor':
20
+ return generateCursorHooks(checkerPath);
21
+ case 'cline':
22
+ return generateClineHooks(checkerPath);
23
+ case 'windsurf':
24
+ return generateWindsurfHooks(checkerPath);
25
+ default:
26
+ return [];
27
+ }
28
+ }
29
+ function generateClaudeHooks(checkerPath) {
30
+ const settings = {
31
+ hooks: {
32
+ PostToolUse: [
33
+ {
34
+ matcher: "Write|Edit|MultiEdit",
35
+ hooks: [
36
+ {
37
+ type: "command",
38
+ command: `node ${checkerPath} --files "$TOOL_INPUT_file_path"`,
39
+ }
40
+ ]
41
+ }
42
+ ]
43
+ }
44
+ };
45
+ return [
46
+ {
47
+ path: '.claude/settings.json',
48
+ content: JSON.stringify(settings, null, 4),
49
+ description: 'Claude Code PostToolUse hook — runs Rigour fast-check after every Write/Edit',
50
+ },
51
+ ];
52
+ }
53
+ function generateCursorHooks(checkerPath) {
54
+ const hooks = {
55
+ version: 1,
56
+ hooks: {
57
+ afterFileEdit: [
58
+ {
59
+ command: `node ${checkerPath} --stdin`,
60
+ }
61
+ ]
62
+ }
63
+ };
64
+ const wrapper = `#!/usr/bin/env node
65
+ /**
66
+ * Cursor afterFileEdit hook wrapper for Rigour.
67
+ * Receives { file_path, old_content, new_content } on stdin.
68
+ * Runs Rigour fast-check on the edited file.
69
+ */
70
+ const { runHookChecker } = require('./node_modules/@rigour-labs/core/dist/hooks/checker.js');
71
+
72
+ let data = '';
73
+ process.stdin.on('data', chunk => { data += chunk; });
74
+ process.stdin.on('end', async () => {
75
+ try {
76
+ const payload = JSON.parse(data);
77
+ const result = await runHookChecker({
78
+ cwd: process.cwd(),
79
+ files: [payload.file_path],
80
+ });
81
+
82
+ // Write result to stdout for Cursor to consume
83
+ process.stdout.write(JSON.stringify({ status: 'ok' }));
84
+
85
+ // Log failures to stderr (visible in Cursor Hooks panel)
86
+ if (result.status === 'fail') {
87
+ for (const f of result.failures) {
88
+ const loc = f.line ? \`:\${f.line}\` : '';
89
+ process.stderr.write(\`[rigour/\${f.gate}] \${f.file}\${loc}: \${f.message}\\n\`);
90
+ }
91
+ }
92
+ } catch (err) {
93
+ process.stderr.write(\`Rigour hook error: \${err.message}\\n\`);
94
+ process.stdout.write(JSON.stringify({ status: 'ok' }));
95
+ }
96
+ });
97
+ `;
98
+ return [
99
+ {
100
+ path: '.cursor/hooks.json',
101
+ content: JSON.stringify(hooks, null, 4),
102
+ description: 'Cursor afterFileEdit hook config',
103
+ },
104
+ {
105
+ path: '.cursor/rigour-hook.js',
106
+ content: wrapper,
107
+ executable: true,
108
+ description: 'Cursor hook wrapper that reads stdin and runs Rigour checker',
109
+ },
110
+ ];
111
+ }
112
+ function generateClineHooks(checkerPath) {
113
+ const script = `#!/usr/bin/env node
114
+ /**
115
+ * Cline PostToolUse hook for Rigour.
116
+ * Receives JSON on stdin with { toolName, toolInput, toolOutput }.
117
+ * Only triggers on write_to_file and replace_in_file tools.
118
+ */
119
+ const { runHookChecker } = require('./node_modules/@rigour-labs/core/dist/hooks/checker.js');
120
+
121
+ const WRITE_TOOLS = ['write_to_file', 'replace_in_file'];
122
+
123
+ let data = '';
124
+ process.stdin.on('data', chunk => { data += chunk; });
125
+ process.stdin.on('end', async () => {
126
+ try {
127
+ const payload = JSON.parse(data);
128
+
129
+ if (!WRITE_TOOLS.includes(payload.toolName)) {
130
+ // Not a write tool, pass through
131
+ process.stdout.write(JSON.stringify({}));
132
+ process.exit(0);
133
+ return;
134
+ }
135
+
136
+ const filePath = payload.toolInput?.path || payload.toolInput?.file_path;
137
+ if (!filePath) {
138
+ process.stdout.write(JSON.stringify({}));
139
+ process.exit(0);
140
+ return;
141
+ }
142
+
143
+ const result = await runHookChecker({
144
+ cwd: process.cwd(),
145
+ files: [filePath],
146
+ });
147
+
148
+ if (result.status === 'fail') {
149
+ const messages = result.failures
150
+ .map(f => {
151
+ const loc = f.line ? \`:\${f.line}\` : '';
152
+ return \`[rigour/\${f.gate}] \${f.file}\${loc}: \${f.message}\`;
153
+ })
154
+ .join('\\n');
155
+
156
+ process.stdout.write(JSON.stringify({
157
+ contextModification: \`\\n[Rigour Quality Gate] Found \${result.failures.length} issue(s):\\n\${messages}\\nPlease fix before continuing.\`,
158
+ }));
159
+ } else {
160
+ process.stdout.write(JSON.stringify({}));
161
+ }
162
+ } catch (err) {
163
+ process.stderr.write(\`Rigour hook error: \${err.message}\\n\`);
164
+ process.stdout.write(JSON.stringify({}));
165
+ }
166
+ });
167
+ `;
168
+ return [
169
+ {
170
+ path: '.clinerules/hooks/PostToolUse',
171
+ content: script,
172
+ executable: true,
173
+ description: 'Cline PostToolUse hook — runs Rigour fast-check after file writes',
174
+ },
175
+ ];
176
+ }
177
+ function generateWindsurfHooks(checkerPath) {
178
+ const hooks = {
179
+ version: 1,
180
+ hooks: {
181
+ post_write_code: [
182
+ {
183
+ command: `node ${checkerPath} --stdin`,
184
+ }
185
+ ]
186
+ }
187
+ };
188
+ const wrapper = `#!/usr/bin/env node
189
+ /**
190
+ * Windsurf post_write_code hook wrapper for Rigour.
191
+ * Receives { file_path, content } on stdin from Cascade agent.
192
+ * Runs Rigour fast-check on the written file.
193
+ */
194
+ const { runHookChecker } = require('./node_modules/@rigour-labs/core/dist/hooks/checker.js');
195
+
196
+ let data = '';
197
+ process.stdin.on('data', chunk => { data += chunk; });
198
+ process.stdin.on('end', async () => {
199
+ try {
200
+ const payload = JSON.parse(data);
201
+ const result = await runHookChecker({
202
+ cwd: process.cwd(),
203
+ files: [payload.file_path],
204
+ });
205
+
206
+ if (result.status === 'fail') {
207
+ for (const f of result.failures) {
208
+ const loc = f.line ? \`:\${f.line}\` : '';
209
+ process.stderr.write(\`[rigour/\${f.gate}] \${f.file}\${loc}: \${f.message}\\n\`);
210
+ }
211
+ // Exit 2 = block (if configured), exit 0 = warn only
212
+ process.exit(0);
213
+ }
214
+ } catch (err) {
215
+ process.stderr.write(\`Rigour hook error: \${err.message}\\n\`);
216
+ }
217
+ });
218
+ `;
219
+ return [
220
+ {
221
+ path: '.windsurf/hooks.json',
222
+ content: JSON.stringify(hooks, null, 4),
223
+ description: 'Windsurf post_write_code hook config',
224
+ },
225
+ {
226
+ path: '.windsurf/rigour-hook.js',
227
+ content: wrapper,
228
+ executable: true,
229
+ description: 'Windsurf hook wrapper that reads stdin and runs Rigour checker',
230
+ },
231
+ ];
232
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Hook system types for multi-tool integration.
3
+ *
4
+ * Each AI coding tool (Claude Code, Cursor, Cline, Windsurf)
5
+ * has its own hook format. These types unify the config generation.
6
+ *
7
+ * @since v3.0.0
8
+ */
9
+ export type HookTool = 'claude' | 'cursor' | 'cline' | 'windsurf';
10
+ export interface HookConfig {
11
+ /** Which tools to generate hooks for */
12
+ tools: HookTool[];
13
+ /** Gates to run in the hook checker (fast subset) */
14
+ fast_gates: string[];
15
+ /** Max execution time in ms before the checker aborts */
16
+ timeout_ms: number;
17
+ /** Whether to block the tool on failure (exit 2) or just warn */
18
+ block_on_failure: boolean;
19
+ }
20
+ /** The fast gates that can run per-file in <200ms */
21
+ export declare const FAST_GATE_IDS: readonly ["hallucinated-imports", "promise-safety", "security-patterns", "file-size"];
22
+ export declare const DEFAULT_HOOK_CONFIG: HookConfig;
23
+ export type FastGateId = typeof FAST_GATE_IDS[number];
24
+ export interface HookCheckerResult {
25
+ status: 'pass' | 'fail' | 'error';
26
+ failures: Array<{
27
+ gate: string;
28
+ file: string;
29
+ message: string;
30
+ severity: string;
31
+ line?: number;
32
+ }>;
33
+ duration_ms: number;
34
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Hook system types for multi-tool integration.
3
+ *
4
+ * Each AI coding tool (Claude Code, Cursor, Cline, Windsurf)
5
+ * has its own hook format. These types unify the config generation.
6
+ *
7
+ * @since v3.0.0
8
+ */
9
+ /** The fast gates that can run per-file in <200ms */
10
+ export const FAST_GATE_IDS = [
11
+ 'hallucinated-imports',
12
+ 'promise-safety',
13
+ 'security-patterns',
14
+ 'file-size',
15
+ ];
16
+ export const DEFAULT_HOOK_CONFIG = {
17
+ tools: ['claude'],
18
+ fast_gates: [...FAST_GATE_IDS],
19
+ timeout_ms: 5000,
20
+ block_on_failure: false,
21
+ };
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export { Gate, GateContext } from './gates/base.js';
8
8
  export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export * from './utils/logger.js';
10
10
  export * from './services/score-history.js';
11
+ export * from './hooks/index.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export { Gate } from './gates/base.js';
8
8
  export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export * from './utils/logger.js';
10
10
  export * from './services/score-history.js';
11
+ export * from './hooks/index.js';
11
12
  // Pattern Index is intentionally NOT exported here to prevent
12
13
  // native dependency issues (sharp/transformers) from leaking into
13
14
  // non-AI parts of the system.
@@ -482,6 +482,13 @@ export const UNIVERSAL_CONFIG = {
482
482
  ignore_patterns: [],
483
483
  },
484
484
  },
485
+ hooks: {
486
+ enabled: false,
487
+ tools: [],
488
+ fast_gates: ['hallucinated-imports', 'promise-safety', 'security-patterns', 'file-size'],
489
+ timeout_ms: 5000,
490
+ block_on_failure: false,
491
+ },
485
492
  output: {
486
493
  report_path: 'rigour-report.json',
487
494
  },
@@ -568,6 +568,25 @@ export declare const CommandsSchema: z.ZodObject<{
568
568
  typecheck?: string | undefined;
569
569
  test?: string | undefined;
570
570
  }>;
571
+ export declare const HooksSchema: z.ZodDefault<z.ZodOptional<z.ZodObject<{
572
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
573
+ tools: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<["claude", "cursor", "cline", "windsurf"]>, "many">>>;
574
+ fast_gates: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
575
+ timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
576
+ block_on_failure: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
577
+ }, "strip", z.ZodTypeAny, {
578
+ enabled: boolean;
579
+ tools: ("claude" | "cursor" | "cline" | "windsurf")[];
580
+ fast_gates: string[];
581
+ timeout_ms: number;
582
+ block_on_failure: boolean;
583
+ }, {
584
+ enabled?: boolean | undefined;
585
+ tools?: ("claude" | "cursor" | "cline" | "windsurf")[] | undefined;
586
+ fast_gates?: string[] | undefined;
587
+ timeout_ms?: number | undefined;
588
+ block_on_failure?: boolean | undefined;
589
+ }>>>;
571
590
  export declare const ConfigSchema: z.ZodObject<{
572
591
  version: z.ZodDefault<z.ZodNumber>;
573
592
  preset: z.ZodOptional<z.ZodString>;
@@ -1141,6 +1160,25 @@ export declare const ConfigSchema: z.ZodObject<{
1141
1160
  check_unsafe_fetch?: boolean | undefined;
1142
1161
  } | undefined;
1143
1162
  }>>>;
1163
+ hooks: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1164
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1165
+ tools: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<["claude", "cursor", "cline", "windsurf"]>, "many">>>;
1166
+ fast_gates: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1167
+ timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1168
+ block_on_failure: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1169
+ }, "strip", z.ZodTypeAny, {
1170
+ enabled: boolean;
1171
+ tools: ("claude" | "cursor" | "cline" | "windsurf")[];
1172
+ fast_gates: string[];
1173
+ timeout_ms: number;
1174
+ block_on_failure: boolean;
1175
+ }, {
1176
+ enabled?: boolean | undefined;
1177
+ tools?: ("claude" | "cursor" | "cline" | "windsurf")[] | undefined;
1178
+ fast_gates?: string[] | undefined;
1179
+ timeout_ms?: number | undefined;
1180
+ block_on_failure?: boolean | undefined;
1181
+ }>>>;
1144
1182
  output: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1145
1183
  report_path: z.ZodDefault<z.ZodString>;
1146
1184
  }, "strip", z.ZodTypeAny, {
@@ -1278,6 +1316,13 @@ export declare const ConfigSchema: z.ZodObject<{
1278
1316
  check_unsafe_fetch: boolean;
1279
1317
  };
1280
1318
  };
1319
+ hooks: {
1320
+ enabled: boolean;
1321
+ tools: ("claude" | "cursor" | "cline" | "windsurf")[];
1322
+ fast_gates: string[];
1323
+ timeout_ms: number;
1324
+ block_on_failure: boolean;
1325
+ };
1281
1326
  output: {
1282
1327
  report_path: string;
1283
1328
  };
@@ -1414,6 +1459,13 @@ export declare const ConfigSchema: z.ZodObject<{
1414
1459
  check_unsafe_fetch?: boolean | undefined;
1415
1460
  } | undefined;
1416
1461
  } | undefined;
1462
+ hooks?: {
1463
+ enabled?: boolean | undefined;
1464
+ tools?: ("claude" | "cursor" | "cline" | "windsurf")[] | undefined;
1465
+ fast_gates?: string[] | undefined;
1466
+ timeout_ms?: number | undefined;
1467
+ block_on_failure?: boolean | undefined;
1468
+ } | undefined;
1417
1469
  output?: {
1418
1470
  report_path?: string | undefined;
1419
1471
  } | undefined;
@@ -1421,9 +1473,11 @@ export declare const ConfigSchema: z.ZodObject<{
1421
1473
  }>;
1422
1474
  export type Gates = z.infer<typeof GatesSchema>;
1423
1475
  export type Commands = z.infer<typeof CommandsSchema>;
1476
+ export type Hooks = z.infer<typeof HooksSchema>;
1424
1477
  export type Config = z.infer<typeof ConfigSchema>;
1425
1478
  export type RawGates = z.input<typeof GatesSchema>;
1426
1479
  export type RawCommands = z.input<typeof CommandsSchema>;
1480
+ export type RawHooks = z.input<typeof HooksSchema>;
1427
1481
  export type RawConfig = z.input<typeof ConfigSchema>;
1428
1482
  export declare const StatusSchema: z.ZodEnum<["PASS", "FAIL", "SKIP", "ERROR"]>;
1429
1483
  export type Status = z.infer<typeof StatusSchema>;
@@ -144,12 +144,25 @@ export const CommandsSchema = z.object({
144
144
  typecheck: z.string().optional(),
145
145
  test: z.string().optional(),
146
146
  });
147
+ export const HooksSchema = z.object({
148
+ enabled: z.boolean().optional().default(false),
149
+ tools: z.array(z.enum(['claude', 'cursor', 'cline', 'windsurf'])).optional().default([]),
150
+ fast_gates: z.array(z.string()).optional().default([
151
+ 'hallucinated-imports',
152
+ 'promise-safety',
153
+ 'security-patterns',
154
+ 'file-size',
155
+ ]),
156
+ timeout_ms: z.number().optional().default(5000),
157
+ block_on_failure: z.boolean().optional().default(false),
158
+ }).optional().default({});
147
159
  export const ConfigSchema = z.object({
148
160
  version: z.number().default(1),
149
161
  preset: z.string().optional(),
150
162
  paradigm: z.string().optional(),
151
163
  commands: CommandsSchema.optional().default({}),
152
164
  gates: GatesSchema.optional().default({}),
165
+ hooks: HooksSchema,
153
166
  output: z.object({
154
167
  report_path: z.string().default('rigour-report.json'),
155
168
  }).optional().default({}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Deterministic quality gate engine for AI-generated code. AST analysis, drift detection, and Fix Packet generation across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",