@rigour-labs/core 1.0.0 → 1.4.0
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/LICENSE +1 -1
- package/dist/discovery.d.ts +18 -2
- package/dist/discovery.js +62 -10
- package/dist/gates/ast.d.ts +10 -0
- package/dist/gates/ast.js +124 -0
- package/dist/gates/dependency.d.ts +7 -0
- package/dist/gates/dependency.js +43 -0
- package/dist/gates/runner.js +6 -0
- package/dist/gates/safety.d.ts +8 -0
- package/dist/gates/safety.js +47 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/services/fix-packet-service.d.ts +6 -0
- package/dist/services/fix-packet-service.js +43 -0
- package/dist/templates/index.d.ts +9 -2
- package/dist/templates/index.js +115 -29
- package/dist/types/fix-packet.d.ts +112 -0
- package/dist/types/fix-packet.js +32 -0
- package/dist/types/index.d.ts +249 -0
- package/dist/types/index.js +23 -0
- package/package.json +5 -2
- package/src/discovery.ts +76 -12
- package/src/gates/ast.ts +150 -0
- package/src/gates/dependency.ts +44 -0
- package/src/gates/runner.ts +6 -0
- package/src/gates/safety.ts +49 -0
- package/src/index.ts +3 -0
- package/src/services/fix-packet-service.ts +42 -0
- package/src/templates/index.ts +123 -30
- package/src/types/fix-packet.ts +32 -0
- package/src/types/index.ts +23 -0
package/src/gates/ast.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { globby } from 'globby';
|
|
5
|
+
import { Gate, GateContext } from './base.js';
|
|
6
|
+
import { Failure, Gates } from '../types/index.js';
|
|
7
|
+
import micromatch from 'micromatch';
|
|
8
|
+
|
|
9
|
+
export class ASTGate extends Gate {
|
|
10
|
+
constructor(private config: Gates) {
|
|
11
|
+
super('ast-analysis', 'AST Structural Analysis');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run(context: GateContext): Promise<Failure[]> {
|
|
15
|
+
const failures: Failure[] = [];
|
|
16
|
+
const files = await globby(['**/*.{ts,js,tsx,jsx}'], {
|
|
17
|
+
cwd: context.cwd,
|
|
18
|
+
ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*'],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const fullPath = path.join(context.cwd, file);
|
|
23
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
24
|
+
const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
25
|
+
|
|
26
|
+
this.analyzeSourceFile(sourceFile, file, failures);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return failures;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private analyzeSourceFile(sourceFile: ts.SourceFile, relativePath: string, failures: Failure[]) {
|
|
33
|
+
const astConfig = this.config.ast || {};
|
|
34
|
+
const maxComplexity = astConfig.complexity || 10;
|
|
35
|
+
const maxMethods = astConfig.max_methods || 10;
|
|
36
|
+
const maxParams = astConfig.max_params || 5;
|
|
37
|
+
|
|
38
|
+
const visit = (node: ts.Node) => {
|
|
39
|
+
// 1. Complexity & Params for functions
|
|
40
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
|
|
41
|
+
const name = this.getNodeName(node);
|
|
42
|
+
|
|
43
|
+
// Parameter count
|
|
44
|
+
if (node.parameters.length > maxParams) {
|
|
45
|
+
failures.push(this.createFailure(
|
|
46
|
+
`Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`,
|
|
47
|
+
[relativePath],
|
|
48
|
+
`Reduce number of parameters or use an options object.`
|
|
49
|
+
));
|
|
50
|
+
// Update: Failures in Runner will be mapped to FixPacket
|
|
51
|
+
(failures[failures.length - 1] as any).metrics = { count: node.parameters.length, max: maxParams };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Cyclomatic Complexity (Simplified: nodes that cause branching)
|
|
55
|
+
let complexity = 1;
|
|
56
|
+
const countComplexity = (n: ts.Node) => {
|
|
57
|
+
if (ts.isIfStatement(n) || ts.isCaseClause(n) || ts.isDefaultClause(n) ||
|
|
58
|
+
ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n) ||
|
|
59
|
+
ts.isWhileStatement(n) || ts.isDoStatement(n) || ts.isConditionalExpression(n)) {
|
|
60
|
+
complexity++;
|
|
61
|
+
}
|
|
62
|
+
if (ts.isBinaryExpression(n)) {
|
|
63
|
+
if (n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
64
|
+
n.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
65
|
+
complexity++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
ts.forEachChild(n, countComplexity);
|
|
69
|
+
};
|
|
70
|
+
ts.forEachChild(node, countComplexity);
|
|
71
|
+
|
|
72
|
+
if (complexity > maxComplexity) {
|
|
73
|
+
failures.push(this.createFailure(
|
|
74
|
+
`Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`,
|
|
75
|
+
[relativePath],
|
|
76
|
+
`Refactor '${name}' into smaller, more focused functions.`
|
|
77
|
+
));
|
|
78
|
+
(failures[failures.length - 1] as any).metrics = { complexity, max: maxComplexity };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Class metrics
|
|
83
|
+
if (ts.isClassDeclaration(node)) {
|
|
84
|
+
const name = node.name?.text || 'Anonymous Class';
|
|
85
|
+
const methods = node.members.filter(ts.isMethodDeclaration);
|
|
86
|
+
|
|
87
|
+
if (methods.length > maxMethods) {
|
|
88
|
+
failures.push(this.createFailure(
|
|
89
|
+
`Class '${name}' has ${methods.length} methods (max: ${maxMethods})`,
|
|
90
|
+
[relativePath],
|
|
91
|
+
`Class '${name}' is becoming a 'God Object'. Split it into smaller services.`
|
|
92
|
+
));
|
|
93
|
+
(failures[failures.length - 1] as any).metrics = { methodCount: methods.length, max: maxMethods };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. Import check for Layer Boundaries
|
|
98
|
+
if (ts.isImportDeclaration(node)) {
|
|
99
|
+
const importPath = (node.moduleSpecifier as ts.StringLiteral).text;
|
|
100
|
+
this.checkBoundary(importPath, relativePath, failures);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ts.forEachChild(node, visit);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
ts.forEachChild(sourceFile, visit);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private checkBoundary(importPath: string, relativePath: string, failures: Failure[]) {
|
|
110
|
+
const boundaries = (this.config as any).architecture?.boundaries || [];
|
|
111
|
+
if (boundaries.length === 0) return;
|
|
112
|
+
|
|
113
|
+
for (const rule of boundaries) {
|
|
114
|
+
const isFromMatch = micromatch.isMatch(relativePath, rule.from);
|
|
115
|
+
if (isFromMatch) {
|
|
116
|
+
// Approximate resolution (simplified for now)
|
|
117
|
+
// Real implementation would need to handle alias and absolute path resolution
|
|
118
|
+
const resolved = importPath.startsWith('.')
|
|
119
|
+
? path.join(path.dirname(relativePath), importPath)
|
|
120
|
+
: importPath;
|
|
121
|
+
|
|
122
|
+
const isToMatch = micromatch.isMatch(resolved, rule.to);
|
|
123
|
+
|
|
124
|
+
if (rule.mode === 'deny' && isToMatch) {
|
|
125
|
+
failures.push(this.createFailure(
|
|
126
|
+
`Architectural Violation: '${relativePath}' is forbidden from importing '${importPath}' (denied by boundary rule).`,
|
|
127
|
+
[relativePath],
|
|
128
|
+
`Remove this import to maintain architectural layering.`
|
|
129
|
+
));
|
|
130
|
+
} else if (rule.mode === 'allow' && !isToMatch && importPath.startsWith('.')) {
|
|
131
|
+
// Complexity: Allow rules are trickier to implement strictly without full resolution
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private getNodeName(node: ts.Node): string {
|
|
138
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
|
|
139
|
+
return node.name?.getText() || 'anonymous';
|
|
140
|
+
}
|
|
141
|
+
if (ts.isArrowFunction(node)) {
|
|
142
|
+
const parent = node.parent;
|
|
143
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
144
|
+
return parent.name.getText();
|
|
145
|
+
}
|
|
146
|
+
return 'anonymous arrow';
|
|
147
|
+
}
|
|
148
|
+
return 'unknown';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Failure, Config } from '../types/index.js';
|
|
4
|
+
import { Gate, GateContext } from './base.js';
|
|
5
|
+
|
|
6
|
+
export class DependencyGate extends Gate {
|
|
7
|
+
constructor(private config: Config) {
|
|
8
|
+
super('dependency-guardian', 'Dependency Guardian');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run(context: GateContext): Promise<Failure[]> {
|
|
12
|
+
const failures: Failure[] = [];
|
|
13
|
+
const forbidden = this.config.gates.dependencies?.forbid || [];
|
|
14
|
+
|
|
15
|
+
if (forbidden.length === 0) return [];
|
|
16
|
+
|
|
17
|
+
const { cwd } = context;
|
|
18
|
+
|
|
19
|
+
// 1. Scan Node.js (package.json)
|
|
20
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
21
|
+
if (await fs.pathExists(pkgPath)) {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = await fs.readJson(pkgPath);
|
|
24
|
+
const allDeps = {
|
|
25
|
+
...(pkg.dependencies || {}),
|
|
26
|
+
...(pkg.devDependencies || {}),
|
|
27
|
+
...(pkg.peerDependencies || {}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const dep of forbidden) {
|
|
31
|
+
if (allDeps[dep]) {
|
|
32
|
+
failures.push(this.createFailure(
|
|
33
|
+
`The package '${dep}' is forbidden by project standards.`,
|
|
34
|
+
['package.json'],
|
|
35
|
+
`Remove '${dep}' from package.json and use approved alternatives.`
|
|
36
|
+
));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (e) { }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return failures;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/gates/runner.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Failure, Config, Report, Status } from '../types/index.js';
|
|
|
3
3
|
import { FileGate } from './file.js';
|
|
4
4
|
import { ContentGate } from './content.js';
|
|
5
5
|
import { StructureGate } from './structure.js';
|
|
6
|
+
import { ASTGate } from './ast.js';
|
|
7
|
+
import { SafetyGate } from './safety.js';
|
|
8
|
+
import { DependencyGate } from './dependency.js';
|
|
6
9
|
import { execa } from 'execa';
|
|
7
10
|
import { Logger } from '../utils/logger.js';
|
|
8
11
|
|
|
@@ -26,6 +29,9 @@ export class GateRunner {
|
|
|
26
29
|
if (this.config.gates.required_files) {
|
|
27
30
|
this.gates.push(new StructureGate({ requiredFiles: this.config.gates.required_files }));
|
|
28
31
|
}
|
|
32
|
+
this.gates.push(new ASTGate(this.config.gates));
|
|
33
|
+
this.gates.push(new DependencyGate(this.config));
|
|
34
|
+
this.gates.push(new SafetyGate(this.config.gates));
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Gate, GateContext } from './base.js';
|
|
2
|
+
import { Failure, Gates } from '../types/index.js';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
|
|
5
|
+
export class SafetyGate extends Gate {
|
|
6
|
+
constructor(private config: Gates) {
|
|
7
|
+
super('safety-rail', 'Safety & Protection Rails');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run(context: GateContext): Promise<Failure[]> {
|
|
11
|
+
const failures: Failure[] = [];
|
|
12
|
+
const safety = this.config.safety || {};
|
|
13
|
+
const protectedPaths = safety.protected_paths || [];
|
|
14
|
+
|
|
15
|
+
if (protectedPaths.length === 0) return [];
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Check for modified files in protected paths using git
|
|
19
|
+
// This is a "Safety Rail" - if an agent touched these, we fail.
|
|
20
|
+
const { stdout } = await execa('git', ['status', '--porcelain'], { cwd: context.cwd });
|
|
21
|
+
const modifiedFiles = stdout.split('\n')
|
|
22
|
+
.filter(line => line.trim().length > 0)
|
|
23
|
+
.map(line => line.slice(3));
|
|
24
|
+
|
|
25
|
+
for (const file of modifiedFiles) {
|
|
26
|
+
if (this.isProtected(file, protectedPaths)) {
|
|
27
|
+
failures.push(this.createFailure(
|
|
28
|
+
`Protected file '${file}' was modified.`,
|
|
29
|
+
[file],
|
|
30
|
+
`Agents are forbidden from modifying files in ${protectedPaths.join(', ')}.`
|
|
31
|
+
));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// If not a git repo, skip safety for now
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return failures;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private isProtected(file: string, patterns: string[]): boolean {
|
|
42
|
+
return patterns.some(p => {
|
|
43
|
+
const cleanP = p.replace('/**', '').replace('/*', '');
|
|
44
|
+
if (file === cleanP) return true;
|
|
45
|
+
if (cleanP.endsWith('/')) return file.startsWith(cleanP);
|
|
46
|
+
return file.startsWith(cleanP + '/');
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './types/index.js';
|
|
2
2
|
export * from './gates/runner.js';
|
|
3
3
|
export * from './discovery.js';
|
|
4
|
+
export * from './services/fix-packet-service.js';
|
|
4
5
|
export * from './templates/index.js';
|
|
6
|
+
export * from './types/fix-packet.js';
|
|
7
|
+
export { Gate, GateContext } from './gates/base.js';
|
|
5
8
|
export * from './utils/logger.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Report, Failure, Config } from '../types/index.js';
|
|
2
|
+
import { FixPacketV2, FixPacketV2Schema } from '../types/fix-packet.js';
|
|
3
|
+
|
|
4
|
+
export class FixPacketService {
|
|
5
|
+
generate(report: Report, config: Config): FixPacketV2 {
|
|
6
|
+
const violations = report.failures.map(f => ({
|
|
7
|
+
id: f.id,
|
|
8
|
+
gate: f.id,
|
|
9
|
+
severity: this.inferSeverity(f),
|
|
10
|
+
title: f.title,
|
|
11
|
+
details: f.details,
|
|
12
|
+
files: f.files,
|
|
13
|
+
hint: f.hint,
|
|
14
|
+
instructions: f.hint ? [f.hint] : [], // Use hint as first instruction
|
|
15
|
+
metrics: (f as any).metrics,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const packet: FixPacketV2 = {
|
|
19
|
+
version: 2,
|
|
20
|
+
goal: "Achieve PASS state by resolving all listed engineering violations.",
|
|
21
|
+
violations,
|
|
22
|
+
constraints: {
|
|
23
|
+
paradigm: config.paradigm,
|
|
24
|
+
protected_paths: config.gates.safety?.protected_paths,
|
|
25
|
+
do_not_touch: config.gates.safety?.protected_paths,
|
|
26
|
+
max_files_changed: config.gates.safety?.max_files_changed_per_cycle,
|
|
27
|
+
no_new_deps: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return FixPacketV2Schema.parse(packet);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private inferSeverity(f: Failure): "low" | "medium" | "high" | "critical" {
|
|
35
|
+
// High complexity or God objects are usually High severity
|
|
36
|
+
if (f.id === 'ast-analysis') return 'high';
|
|
37
|
+
// Unit test or Lint failures are Medium
|
|
38
|
+
if (f.id === 'test' || f.id === 'lint') return 'medium';
|
|
39
|
+
// Documentation or small file size issues are Low
|
|
40
|
+
return 'medium';
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/templates/index.ts
CHANGED
|
@@ -1,60 +1,137 @@
|
|
|
1
|
-
import { Config } from '../types/index.js';
|
|
1
|
+
import { Config, Commands, Gates } from '../types/index.js';
|
|
2
2
|
|
|
3
3
|
export interface Template {
|
|
4
4
|
name: string;
|
|
5
5
|
markers: string[];
|
|
6
|
-
config:
|
|
6
|
+
config: {
|
|
7
|
+
preset?: string;
|
|
8
|
+
paradigm?: string;
|
|
9
|
+
commands?: Partial<Commands>;
|
|
10
|
+
gates?: Partial<Gates>;
|
|
11
|
+
planned?: string[];
|
|
12
|
+
};
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
export const TEMPLATES: Template[] = [
|
|
10
16
|
{
|
|
11
|
-
name: '
|
|
12
|
-
markers: [
|
|
17
|
+
name: 'ui',
|
|
18
|
+
markers: [
|
|
19
|
+
'react',
|
|
20
|
+
'next',
|
|
21
|
+
'vue',
|
|
22
|
+
'svelte',
|
|
23
|
+
'next.config.js',
|
|
24
|
+
'vite.config.ts',
|
|
25
|
+
'tailwind.config.js',
|
|
26
|
+
'base.css',
|
|
27
|
+
'index.html',
|
|
28
|
+
],
|
|
13
29
|
config: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
preset: 'ui',
|
|
31
|
+
gates: {
|
|
32
|
+
max_file_lines: 300,
|
|
33
|
+
required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
|
|
17
34
|
},
|
|
35
|
+
planned: [
|
|
36
|
+
'Layer Boundary: Components cannot import from DB',
|
|
37
|
+
'Prop-Drilling Detection: Max depth 5',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'api',
|
|
43
|
+
markers: [
|
|
44
|
+
'express',
|
|
45
|
+
'fastify',
|
|
46
|
+
'nestjs',
|
|
47
|
+
'go.mod',
|
|
48
|
+
'requirements.txt',
|
|
49
|
+
'pyproject.toml',
|
|
50
|
+
'app.py',
|
|
51
|
+
'main.go',
|
|
52
|
+
'index.js',
|
|
53
|
+
],
|
|
54
|
+
config: {
|
|
55
|
+
preset: 'api',
|
|
18
56
|
gates: {
|
|
19
|
-
max_file_lines:
|
|
20
|
-
forbid_todos: true,
|
|
21
|
-
forbid_fixme: true,
|
|
22
|
-
forbid_paths: [],
|
|
57
|
+
max_file_lines: 400,
|
|
23
58
|
required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
|
|
24
59
|
},
|
|
60
|
+
planned: [
|
|
61
|
+
'Service Layer Enforcement: Controllers -> Services only',
|
|
62
|
+
'Repo Pattern: Databases access isolated to repositories/',
|
|
63
|
+
],
|
|
25
64
|
},
|
|
26
65
|
},
|
|
27
66
|
{
|
|
28
|
-
name: '
|
|
29
|
-
markers: [
|
|
67
|
+
name: 'infra',
|
|
68
|
+
markers: [
|
|
69
|
+
'Dockerfile',
|
|
70
|
+
'docker-compose.yml',
|
|
71
|
+
'main.tf',
|
|
72
|
+
'k8s/',
|
|
73
|
+
'helm/',
|
|
74
|
+
'ansible/',
|
|
75
|
+
],
|
|
30
76
|
config: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
77
|
+
preset: 'infra',
|
|
78
|
+
gates: {
|
|
79
|
+
max_file_lines: 300,
|
|
80
|
+
required_files: ['docs/RUNBOOK.md', 'docs/ARCH.md', 'README.md'],
|
|
34
81
|
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'data',
|
|
86
|
+
markers: [
|
|
87
|
+
'ipynb',
|
|
88
|
+
'spark',
|
|
89
|
+
'pandas',
|
|
90
|
+
'data/',
|
|
91
|
+
'dbt_project.yml',
|
|
92
|
+
],
|
|
93
|
+
config: {
|
|
94
|
+
preset: 'data',
|
|
35
95
|
gates: {
|
|
36
96
|
max_file_lines: 500,
|
|
37
|
-
|
|
38
|
-
forbid_fixme: true,
|
|
39
|
-
forbid_paths: [],
|
|
40
|
-
required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
|
|
97
|
+
required_files: ['docs/DATA_DICTIONARY.md', 'docs/PIPELINE.md', 'README.md'],
|
|
41
98
|
},
|
|
99
|
+
planned: [
|
|
100
|
+
'Stochastic Determinism: Seed setting enforcement',
|
|
101
|
+
'Data Leaks: Detecting PII in notebook outputs',
|
|
102
|
+
],
|
|
42
103
|
},
|
|
43
104
|
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
export const PARADIGM_TEMPLATES: Template[] = [
|
|
44
108
|
{
|
|
45
|
-
name: '
|
|
46
|
-
markers: [
|
|
109
|
+
name: 'oop',
|
|
110
|
+
markers: [
|
|
111
|
+
'class ',
|
|
112
|
+
'interface ',
|
|
113
|
+
'implements ',
|
|
114
|
+
'extends ',
|
|
115
|
+
],
|
|
47
116
|
config: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
117
|
+
paradigm: 'oop',
|
|
118
|
+
gates: {
|
|
119
|
+
// Future: class-specific gates
|
|
51
120
|
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'functional',
|
|
125
|
+
markers: [
|
|
126
|
+
'=>',
|
|
127
|
+
'export const',
|
|
128
|
+
'map(',
|
|
129
|
+
'filter(',
|
|
130
|
+
],
|
|
131
|
+
config: {
|
|
132
|
+
paradigm: 'functional',
|
|
52
133
|
gates: {
|
|
53
|
-
|
|
54
|
-
forbid_todos: true,
|
|
55
|
-
forbid_fixme: true,
|
|
56
|
-
forbid_paths: [],
|
|
57
|
-
required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
|
|
134
|
+
// Future: function-specific gates
|
|
58
135
|
},
|
|
59
136
|
},
|
|
60
137
|
},
|
|
@@ -69,8 +146,24 @@ export const UNIVERSAL_CONFIG: Config = {
|
|
|
69
146
|
forbid_fixme: true,
|
|
70
147
|
forbid_paths: [],
|
|
71
148
|
required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'docs/DECISIONS.md', 'docs/TASKS.md'],
|
|
149
|
+
ast: {
|
|
150
|
+
complexity: 10,
|
|
151
|
+
max_methods: 10,
|
|
152
|
+
max_params: 5,
|
|
153
|
+
},
|
|
154
|
+
dependencies: {
|
|
155
|
+
forbid: [],
|
|
156
|
+
},
|
|
157
|
+
architecture: {
|
|
158
|
+
boundaries: [],
|
|
159
|
+
},
|
|
160
|
+
safety: {
|
|
161
|
+
max_files_changed_per_cycle: 10,
|
|
162
|
+
protected_paths: ['.github/**', 'docs/**', 'rigour.yml'],
|
|
163
|
+
},
|
|
72
164
|
},
|
|
73
165
|
output: {
|
|
74
166
|
report_path: 'rigour-report.json',
|
|
75
167
|
},
|
|
168
|
+
planned: [],
|
|
76
169
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fix Packet v2 Schema
|
|
5
|
+
* Designed for high-fidelity communication with AI agents during the refinement loop.
|
|
6
|
+
*/
|
|
7
|
+
export const FixPacketV2Schema = z.object({
|
|
8
|
+
version: z.literal(2),
|
|
9
|
+
goal: z.string().default('Achieve PASS state for all quality gates'),
|
|
10
|
+
violations: z.array(z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
gate: z.string(),
|
|
13
|
+
severity: z.enum(['low', 'medium', 'high', 'critical']).default('medium'),
|
|
14
|
+
category: z.string().optional(),
|
|
15
|
+
title: z.string(),
|
|
16
|
+
details: z.string(),
|
|
17
|
+
files: z.array(z.string()).optional(),
|
|
18
|
+
hint: z.string().optional(),
|
|
19
|
+
instructions: z.array(z.string()).optional(), // Step-by-step fix instructions
|
|
20
|
+
metrics: z.record(z.any()).optional(), // e.g., { complexity: 15, max: 10 }
|
|
21
|
+
})),
|
|
22
|
+
constraints: z.object({
|
|
23
|
+
protected_paths: z.array(z.string()).optional(),
|
|
24
|
+
do_not_touch: z.array(z.string()).optional(), // Alias for protected_paths
|
|
25
|
+
max_files_changed: z.number().optional(),
|
|
26
|
+
no_new_deps: z.boolean().optional().default(true),
|
|
27
|
+
allowed_dependencies: z.array(z.string()).optional(),
|
|
28
|
+
paradigm: z.string().optional(), // 'oop', 'functional'
|
|
29
|
+
}).optional().default({}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type FixPacketV2 = z.infer<typeof FixPacketV2Schema>;
|
package/src/types/index.ts
CHANGED
|
@@ -11,6 +11,26 @@ export const GatesSchema = z.object({
|
|
|
11
11
|
'docs/DECISIONS.md',
|
|
12
12
|
'docs/TASKS.md',
|
|
13
13
|
]),
|
|
14
|
+
ast: z.object({
|
|
15
|
+
complexity: z.number().optional().default(10),
|
|
16
|
+
max_methods: z.number().optional().default(10),
|
|
17
|
+
max_params: z.number().optional().default(5),
|
|
18
|
+
}).optional().default({}),
|
|
19
|
+
dependencies: z.object({
|
|
20
|
+
forbid: z.array(z.string()).optional().default([]),
|
|
21
|
+
trusted_registry: z.string().optional(),
|
|
22
|
+
}).optional().default({}),
|
|
23
|
+
architecture: z.object({
|
|
24
|
+
boundaries: z.array(z.object({
|
|
25
|
+
from: z.string(),
|
|
26
|
+
to: z.string(),
|
|
27
|
+
mode: z.enum(['allow', 'deny']).default('deny'),
|
|
28
|
+
})).optional().default([]),
|
|
29
|
+
}).optional().default({}),
|
|
30
|
+
safety: z.object({
|
|
31
|
+
max_files_changed_per_cycle: z.number().optional().default(10),
|
|
32
|
+
protected_paths: z.array(z.string()).optional().default(['.github/**', 'docs/**', 'rigour.yml']),
|
|
33
|
+
}).optional().default({}),
|
|
14
34
|
});
|
|
15
35
|
|
|
16
36
|
export const CommandsSchema = z.object({
|
|
@@ -22,11 +42,14 @@ export const CommandsSchema = z.object({
|
|
|
22
42
|
|
|
23
43
|
export const ConfigSchema = z.object({
|
|
24
44
|
version: z.number().default(1),
|
|
45
|
+
preset: z.string().optional(),
|
|
46
|
+
paradigm: z.string().optional(),
|
|
25
47
|
commands: CommandsSchema.optional().default({}),
|
|
26
48
|
gates: GatesSchema.optional().default({}),
|
|
27
49
|
output: z.object({
|
|
28
50
|
report_path: z.string().default('rigour-report.json'),
|
|
29
51
|
}).optional().default({}),
|
|
52
|
+
planned: z.array(z.string()).optional().default([]),
|
|
30
53
|
});
|
|
31
54
|
|
|
32
55
|
export type Gates = z.infer<typeof GatesSchema>;
|