@neurcode-ai/governance-runtime 0.1.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.
@@ -0,0 +1,19 @@
1
+ export type DeterministicConstraintSource = 'intent' | 'policy';
2
+ export interface DeterministicConstraintRule {
3
+ id: string;
4
+ source: DeterministicConstraintSource;
5
+ statement: string;
6
+ displayName: string;
7
+ pattern: RegExp;
8
+ matchToken: string;
9
+ }
10
+ export interface DeterministicConstraintCompilation {
11
+ rules: DeterministicConstraintRule[];
12
+ unmatchedStatements: string[];
13
+ }
14
+ export interface DeterministicConstraintCompilationInput {
15
+ intentConstraints?: string;
16
+ policyRules?: string[];
17
+ }
18
+ export declare function compileDeterministicConstraints(input: DeterministicConstraintCompilationInput): DeterministicConstraintCompilation;
19
+ //# sourceMappingURL=constraints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraints.d.ts","sourceRoot":"","sources":["../src/constraints.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,6BAA6B,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,6BAA6B,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kCAAkC;IACjD,KAAK,EAAE,2BAA2B,EAAE,CAAC;IACrC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,uCAAuC;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAoJD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,uCAAuC,GAC7C,kCAAkC,CAapC"}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileDeterministicConstraints = compileDeterministicConstraints;
4
+ const PROHIBITIVE_PATTERN = /\b(no|do not|don't|without|avoid|ban|disallow|never|must not)\b/i;
5
+ const CONSTRAINT_TEMPLATES = [
6
+ {
7
+ id: 'no_useeffect',
8
+ displayName: 'No useEffect',
9
+ triggerTokens: ['useeffect'],
10
+ pattern: /\buseEffect\s*\(/i,
11
+ matchToken: 'useeffect',
12
+ },
13
+ {
14
+ id: 'no_console_log',
15
+ displayName: 'No console.log',
16
+ triggerTokens: ['console.log', 'console log'],
17
+ pattern: /\bconsole\.log\s*\(/i,
18
+ matchToken: 'console.log',
19
+ },
20
+ {
21
+ id: 'no_debugger',
22
+ displayName: 'No debugger statements',
23
+ triggerTokens: ['debugger'],
24
+ pattern: /\bdebugger\b/i,
25
+ matchToken: 'debugger',
26
+ },
27
+ {
28
+ id: 'no_eval',
29
+ displayName: 'No eval usage',
30
+ triggerTokens: ['eval'],
31
+ pattern: /\beval\s*\(/i,
32
+ matchToken: 'eval',
33
+ },
34
+ {
35
+ id: 'no_process_env',
36
+ displayName: 'No process.env access',
37
+ triggerTokens: ['process.env', 'process env'],
38
+ pattern: /\bprocess\.env\b/i,
39
+ matchToken: 'process.env',
40
+ },
41
+ {
42
+ id: 'no_any_type',
43
+ displayName: 'No any type',
44
+ triggerTokens: [' any', 'type any', ': any', '<any>'],
45
+ pattern: /(:\s*any\b|<\s*any\s*>|\bArray<any>\b|\bPromise<any>\b)/i,
46
+ matchToken: 'any',
47
+ },
48
+ {
49
+ id: 'no_todo_fixme',
50
+ displayName: 'No TODO/FIXME markers',
51
+ triggerTokens: ['todo', 'fixme'],
52
+ pattern: /\b(TODO|FIXME)\b/i,
53
+ matchToken: 'todo/fixme',
54
+ },
55
+ ];
56
+ function splitStatements(raw) {
57
+ return raw
58
+ .split(/[\n;]+/)
59
+ .map((part) => part.trim())
60
+ .filter(Boolean);
61
+ }
62
+ function normalizeStatement(statement) {
63
+ return statement
64
+ .replace(/\s+/g, ' ')
65
+ .trim()
66
+ .toLowerCase();
67
+ }
68
+ function statementMatchesTemplate(normalizedStatement, template) {
69
+ return template.triggerTokens.some((token) => normalizedStatement.includes(token));
70
+ }
71
+ function createRule(template, source, statement) {
72
+ return {
73
+ id: `${source}:${template.id}`,
74
+ source,
75
+ statement,
76
+ displayName: template.displayName,
77
+ pattern: template.pattern,
78
+ matchToken: template.matchToken,
79
+ };
80
+ }
81
+ function compileStatements(statements, source) {
82
+ const rules = [];
83
+ const unmatchedStatements = [];
84
+ for (const rawStatement of statements) {
85
+ const normalized = normalizeStatement(rawStatement);
86
+ if (!normalized) {
87
+ continue;
88
+ }
89
+ const requiresProhibitiveLanguage = source === 'intent';
90
+ if (requiresProhibitiveLanguage && !PROHIBITIVE_PATTERN.test(normalized)) {
91
+ continue;
92
+ }
93
+ const matches = CONSTRAINT_TEMPLATES.filter((template) => statementMatchesTemplate(normalized, template));
94
+ if (matches.length === 0) {
95
+ unmatchedStatements.push(rawStatement);
96
+ continue;
97
+ }
98
+ for (const match of matches) {
99
+ rules.push(createRule(match, source, rawStatement));
100
+ }
101
+ }
102
+ return {
103
+ rules,
104
+ unmatchedStatements,
105
+ };
106
+ }
107
+ function dedupeRules(rules) {
108
+ const seen = new Set();
109
+ const deduped = [];
110
+ for (const rule of rules) {
111
+ const key = `${rule.id}::${rule.statement.toLowerCase()}`;
112
+ if (seen.has(key)) {
113
+ continue;
114
+ }
115
+ seen.add(key);
116
+ deduped.push(rule);
117
+ }
118
+ return deduped;
119
+ }
120
+ function compileDeterministicConstraints(input) {
121
+ const intentStatements = splitStatements(input.intentConstraints || '');
122
+ const policyStatements = (input.policyRules || [])
123
+ .map((rule) => String(rule || '').trim())
124
+ .filter(Boolean);
125
+ const compiledIntent = compileStatements(intentStatements, 'intent');
126
+ const compiledPolicy = compileStatements(policyStatements, 'policy');
127
+ return {
128
+ rules: dedupeRules([...compiledIntent.rules, ...compiledPolicy.rules]),
129
+ unmatchedStatements: [...compiledIntent.unmatchedStatements, ...compiledPolicy.unmatchedStatements],
130
+ };
131
+ }
132
+ //# sourceMappingURL=constraints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraints.js","sourceRoot":"","sources":["../src/constraints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ export type PlanVerdict = 'PASS' | 'FAIL' | 'WARN';
2
+ export { compileDeterministicConstraints, type DeterministicConstraintCompilation, type DeterministicConstraintCompilationInput, type DeterministicConstraintRule, type DeterministicConstraintSource, } from './constraints';
3
+ import { type DeterministicConstraintRule } from './constraints';
4
+ export interface PlanFileScopeItem {
5
+ path: string;
6
+ action: string;
7
+ }
8
+ export interface PlanDiffLine {
9
+ type: 'context' | 'added' | 'removed';
10
+ content: string;
11
+ lineNumber?: number;
12
+ }
13
+ export interface PlanDiffHunk {
14
+ oldStart: number;
15
+ oldLines: number;
16
+ newStart: number;
17
+ newLines: number;
18
+ lines: PlanDiffLine[];
19
+ }
20
+ export interface PlanDiffFile {
21
+ path: string;
22
+ oldPath?: string;
23
+ changeType: 'add' | 'delete' | 'modify' | 'rename';
24
+ added: number;
25
+ removed: number;
26
+ hunks?: PlanDiffHunk[];
27
+ }
28
+ export interface PlanDiffStats {
29
+ totalAdded: number;
30
+ totalRemoved: number;
31
+ totalFiles: number;
32
+ }
33
+ export interface PlanVerificationInput {
34
+ planFiles: PlanFileScopeItem[];
35
+ changedFiles: PlanDiffFile[];
36
+ diffStats?: PlanDiffStats;
37
+ intentConstraints?: string;
38
+ policyRules?: string[];
39
+ extraConstraintRules?: DeterministicConstraintRule[];
40
+ }
41
+ export interface PlanDiffSummary {
42
+ added: number;
43
+ removed: number;
44
+ files: Array<{
45
+ path: string;
46
+ oldPath?: string;
47
+ changeType: string;
48
+ added: number;
49
+ removed: number;
50
+ hunks: PlanDiffHunk[];
51
+ }>;
52
+ bloatFiles: string[];
53
+ plannedFilesModified: number;
54
+ totalPlannedFiles: number;
55
+ }
56
+ export interface PlanVerificationResult {
57
+ adherenceScore: number;
58
+ bloatCount: number;
59
+ bloatFiles: string[];
60
+ plannedFilesModified: number;
61
+ totalPlannedFiles: number;
62
+ scopeGuardPassed: boolean;
63
+ constraintViolations: string[];
64
+ verdict: PlanVerdict;
65
+ diffSummary: PlanDiffSummary;
66
+ message: string;
67
+ }
68
+ export declare function extractPlannedFilePaths(planFiles: PlanFileScopeItem[]): string[];
69
+ export declare function resolvePlanVerdict(input: {
70
+ bloatCount: number;
71
+ adherenceScore: number;
72
+ totalPlannedFiles: number;
73
+ plannedFilesModified: number;
74
+ constraintViolations: string[];
75
+ }): PlanVerdict;
76
+ export declare function buildPlanVerificationMessage(result: Pick<PlanVerificationResult, 'constraintViolations' | 'totalPlannedFiles' | 'plannedFilesModified' | 'verdict' | 'adherenceScore' | 'bloatCount'>): string;
77
+ export declare function evaluatePlanVerification(input: PlanVerificationInput): PlanVerificationResult;
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileDeterministicConstraints = void 0;
4
+ exports.extractPlannedFilePaths = extractPlannedFilePaths;
5
+ exports.resolvePlanVerdict = resolvePlanVerdict;
6
+ exports.buildPlanVerificationMessage = buildPlanVerificationMessage;
7
+ exports.evaluatePlanVerification = evaluatePlanVerification;
8
+ var constraints_1 = require("./constraints");
9
+ Object.defineProperty(exports, "compileDeterministicConstraints", { enumerable: true, get: function () { return constraints_1.compileDeterministicConstraints; } });
10
+ const constraints_2 = require("./constraints");
11
+ function normalizeRepoPath(pathValue) {
12
+ return pathValue.replace(/\\/g, '/').replace(/^\.\//, '').trim();
13
+ }
14
+ function extractPlannedFilePaths(planFiles) {
15
+ const plannedFilePaths = new Set();
16
+ for (const file of planFiles) {
17
+ const normalizedPath = normalizeRepoPath(file.path);
18
+ if (!normalizedPath) {
19
+ continue;
20
+ }
21
+ if (file.action === 'MODIFY' || file.action === 'CREATE') {
22
+ plannedFilePaths.add(normalizedPath);
23
+ }
24
+ }
25
+ return Array.from(plannedFilePaths);
26
+ }
27
+ function detectConstraintViolations(intentConstraints, policyRules, extraConstraintRules, changedFiles) {
28
+ const compiled = (0, constraints_2.compileDeterministicConstraints)({
29
+ intentConstraints,
30
+ policyRules,
31
+ });
32
+ const rules = [
33
+ ...compiled.rules,
34
+ ...(extraConstraintRules || []),
35
+ ];
36
+ if (rules.length === 0) {
37
+ return [];
38
+ }
39
+ const violations = [];
40
+ const seenViolations = new Set();
41
+ for (const rule of rules) {
42
+ for (const file of changedFiles) {
43
+ if (!file.hunks || file.hunks.length === 0) {
44
+ continue;
45
+ }
46
+ for (const hunk of file.hunks) {
47
+ for (const line of hunk.lines) {
48
+ if (line.type !== 'added') {
49
+ continue;
50
+ }
51
+ if (!rule.pattern.test(line.content)) {
52
+ continue;
53
+ }
54
+ const violation = `${rule.matchToken} found in ${file.path} (violates constraint: \"${rule.displayName}\")`;
55
+ if (seenViolations.has(violation)) {
56
+ continue;
57
+ }
58
+ seenViolations.add(violation);
59
+ violations.push(violation);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return violations;
65
+ }
66
+ function resolvePlanVerdict(input) {
67
+ if (input.constraintViolations.length > 0) {
68
+ return 'FAIL';
69
+ }
70
+ if (input.totalPlannedFiles === 0 && input.plannedFilesModified === 0) {
71
+ // 0/0 is treated as incomplete, not perfect adherence.
72
+ return 'FAIL';
73
+ }
74
+ if (input.bloatCount > 0 && input.adherenceScore < 50) {
75
+ return 'FAIL';
76
+ }
77
+ if (input.bloatCount > 0 || input.adherenceScore < 80) {
78
+ return 'WARN';
79
+ }
80
+ return 'PASS';
81
+ }
82
+ function buildPlanVerificationMessage(result) {
83
+ if (result.constraintViolations.length > 0) {
84
+ return `❌ Constraint violation: ${result.constraintViolations[0]}${result.constraintViolations.length > 1 ? ` (+${result.constraintViolations.length - 1} more)` : ''}`;
85
+ }
86
+ if (result.totalPlannedFiles === 0 && result.plannedFilesModified === 0) {
87
+ return '❌ Incomplete: No planned files to verify (0/0). Plan may be malformed.';
88
+ }
89
+ if (result.verdict === 'PASS') {
90
+ return `✅ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)`;
91
+ }
92
+ if (result.verdict === 'WARN') {
93
+ return `⚠️ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)${result.bloatCount > 0 ? `, ${result.bloatCount} unexpected file(s) changed` : ''}`;
94
+ }
95
+ return `❌ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified), ${result.bloatCount} unexpected file(s) changed`;
96
+ }
97
+ function evaluatePlanVerification(input) {
98
+ const plannedFilePaths = extractPlannedFilePaths(input.planFiles);
99
+ const plannedSet = new Set(plannedFilePaths);
100
+ const normalizedChangedFiles = input.changedFiles.map((file) => ({
101
+ ...file,
102
+ path: normalizeRepoPath(file.path),
103
+ oldPath: file.oldPath ? normalizeRepoPath(file.oldPath) : undefined,
104
+ hunks: file.hunks || [],
105
+ }));
106
+ const changedFilePaths = Array.from(new Set(normalizedChangedFiles
107
+ .map((file) => file.path)
108
+ .filter(Boolean)));
109
+ const changedSet = new Set(changedFilePaths);
110
+ const bloatFiles = changedFilePaths.filter((pathValue) => !plannedSet.has(pathValue));
111
+ const bloatCount = bloatFiles.length;
112
+ const totalPlannedFiles = plannedSet.size;
113
+ const plannedFilesModified = plannedFilePaths.filter((pathValue) => changedSet.has(pathValue)).length;
114
+ const adherenceScore = totalPlannedFiles > 0
115
+ ? Math.round((plannedFilesModified / totalPlannedFiles) * 100)
116
+ : 0;
117
+ const constraintViolations = detectConstraintViolations(input.intentConstraints, input.policyRules, input.extraConstraintRules, normalizedChangedFiles);
118
+ const verdict = resolvePlanVerdict({
119
+ bloatCount,
120
+ adherenceScore,
121
+ totalPlannedFiles,
122
+ plannedFilesModified,
123
+ constraintViolations,
124
+ });
125
+ const totalAdded = input.diffStats
126
+ ? input.diffStats.totalAdded
127
+ : normalizedChangedFiles.reduce((sum, file) => sum + file.added, 0);
128
+ const totalRemoved = input.diffStats
129
+ ? input.diffStats.totalRemoved
130
+ : normalizedChangedFiles.reduce((sum, file) => sum + file.removed, 0);
131
+ const diffSummary = {
132
+ added: totalAdded,
133
+ removed: totalRemoved,
134
+ files: normalizedChangedFiles.map((file) => ({
135
+ path: file.path,
136
+ oldPath: file.oldPath,
137
+ changeType: file.changeType,
138
+ added: file.added,
139
+ removed: file.removed,
140
+ hunks: file.hunks || [],
141
+ })),
142
+ bloatFiles,
143
+ plannedFilesModified,
144
+ totalPlannedFiles,
145
+ };
146
+ const message = buildPlanVerificationMessage({
147
+ constraintViolations,
148
+ totalPlannedFiles,
149
+ plannedFilesModified,
150
+ verdict,
151
+ adherenceScore,
152
+ bloatCount,
153
+ });
154
+ return {
155
+ adherenceScore,
156
+ bloatCount,
157
+ bloatFiles,
158
+ plannedFilesModified,
159
+ totalPlannedFiles,
160
+ scopeGuardPassed: bloatCount === 0,
161
+ constraintViolations,
162
+ verdict,
163
+ diffSummary,
164
+ message,
165
+ };
166
+ }
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAsFA,0DAeC;AAoDD,gDAyBC;AAED,oEAqBC;AAED,4DAuFC;AAjSD,6CAMuB;AALrB,8HAAA,+BAA+B,OAAA;AAMjC,+CAAkG;AA0ElG,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACnE,CAAC;AAED,SAAgB,uBAAuB,CAAC,SAA8B;IACpE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzD,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,0BAA0B,CACjC,iBAAqC,EACrC,WAAiC,EACjC,oBAA+D,EAC/D,YAA4B;IAE5B,MAAM,QAAQ,GAAG,IAAA,6CAA+B,EAAC;QAC/C,iBAAiB;QACjB,WAAW;KACZ,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG;QACZ,GAAG,QAAQ,CAAC,KAAK;QACjB,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC;KAChC,CAAC;IAEF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3C,SAAS;YACX,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;wBAC1B,SAAS;oBACX,CAAC;oBACD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;wBACrC,SAAS;oBACX,CAAC;oBACD,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,UAAU,aAAa,IAAI,CAAC,IAAI,4BAA4B,IAAI,CAAC,WAAW,KAAK,CAAC;oBAC5G,IAAI,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;wBAClC,SAAS;oBACX,CAAC;oBACD,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;oBAC9B,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAgB,kBAAkB,CAAC,KAMlC;IACC,IAAI,KAAK,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,IAAI,KAAK,CAAC,oBAAoB,KAAK,CAAC,EAAE,CAAC;QACtE,uDAAuD;QACvD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,KAAK,CAAC,cAAc,GAAG,EAAE,EAAE,CAAC;QACtD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,KAAK,CAAC,cAAc,GAAG,EAAE,EAAE,CAAC;QACtD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAgB,4BAA4B,CAAC,MAG5C;IACC,IAAI,MAAM,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,2BAA2B,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC1K,CAAC;IAED,IAAI,MAAM,CAAC,iBAAiB,KAAK,CAAC,IAAI,MAAM,CAAC,oBAAoB,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,wEAAwE,CAAC;IAClF,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,qBAAqB,MAAM,CAAC,cAAc,MAAM,MAAM,CAAC,oBAAoB,IAAI,MAAM,CAAC,iBAAiB,0BAA0B,CAAC;IAC3I,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,uBAAuB,MAAM,CAAC,cAAc,MAAM,MAAM,CAAC,oBAAoB,IAAI,MAAM,CAAC,iBAAiB,2BAA2B,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,UAAU,6BAA6B,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAChO,CAAC;IAED,OAAO,qBAAqB,MAAM,CAAC,cAAc,MAAM,MAAM,CAAC,oBAAoB,IAAI,MAAM,CAAC,iBAAiB,6BAA6B,MAAM,CAAC,UAAU,6BAA6B,CAAC;AAC5L,CAAC;AAED,SAAgB,wBAAwB,CAAC,KAA4B;IACnE,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE7C,MAAM,sBAAsB,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/D,GAAG,IAAI;QACP,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAClC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QACnE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;KACxB,CAAC,CAAC,CAAC;IAEJ,MAAM,gBAAgB,GAAG,KAAK,CAAC,IAAI,CACjC,IAAI,GAAG,CACL,sBAAsB;SACnB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACxB,MAAM,CAAC,OAAO,CAAC,CACnB,CACF,CAAC;IACF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE7C,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC;IAErC,MAAM,iBAAiB,GAAG,UAAU,CAAC,IAAI,CAAC;IAC1C,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;IACtG,MAAM,cAAc,GAAG,iBAAiB,GAAG,CAAC;QAC1C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,oBAAoB,GAAG,iBAAiB,CAAC,GAAG,GAAG,CAAC;QAC9D,CAAC,CAAC,CAAC,CAAC;IAEN,MAAM,oBAAoB,GAAG,0BAA0B,CACrD,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,oBAAoB,EAC1B,sBAAsB,CACvB,CAAC;IACF,MAAM,OAAO,GAAG,kBAAkB,CAAC;QACjC,UAAU;QACV,cAAc;QACd,iBAAiB;QACjB,oBAAoB;QACpB,oBAAoB;KACrB,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS;QAChC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU;QAC5B,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,YAAY,GAAG,KAAK,CAAC,SAAS;QAClC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,YAAY;QAC9B,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAExE,MAAM,WAAW,GAAoB;QACnC,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,YAAY;QACrB,KAAK,EAAE,sBAAsB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;SACxB,CAAC,CAAC;QACH,UAAU;QACV,oBAAoB;QACpB,iBAAiB;KAClB,CAAC;IAEF,MAAM,OAAO,GAAG,4BAA4B,CAAC;QAC3C,oBAAoB;QACpB,iBAAiB;QACjB,oBAAoB;QACpB,OAAO;QACP,cAAc;QACd,UAAU;KACX,CAAC,CAAC;IAEH,OAAO;QACL,cAAc;QACd,UAAU;QACV,UAAU;QACV,oBAAoB;QACpB,iBAAiB;QACjB,gBAAgB,EAAE,UAAU,KAAK,CAAC;QAClC,oBAAoB;QACpB,OAAO;QACP,WAAW;QACX,OAAO;KACR,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@neurcode-ai/governance-runtime",
3
+ "version": "0.1.0",
4
+ "description": "Deterministic plan/diff governance runtime shared by CLI, API, and integrations",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "test": "tsx --test src/*.test.ts"
11
+ },
12
+ "license": "MIT",
13
+ "dependencies": {},
14
+ "devDependencies": {
15
+ "@types/node": "^20.10.0",
16
+ "tsx": "^4.20.6",
17
+ "typescript": "^5.3.0"
18
+ }
19
+ }
@@ -0,0 +1,183 @@
1
+ export type DeterministicConstraintSource = 'intent' | 'policy';
2
+
3
+ export interface DeterministicConstraintRule {
4
+ id: string;
5
+ source: DeterministicConstraintSource;
6
+ statement: string;
7
+ displayName: string;
8
+ pattern: RegExp;
9
+ matchToken: string;
10
+ }
11
+
12
+ export interface DeterministicConstraintCompilation {
13
+ rules: DeterministicConstraintRule[];
14
+ unmatchedStatements: string[];
15
+ }
16
+
17
+ export interface DeterministicConstraintCompilationInput {
18
+ intentConstraints?: string;
19
+ policyRules?: string[];
20
+ }
21
+
22
+ interface ConstraintTemplate {
23
+ id: string;
24
+ displayName: string;
25
+ triggerTokens: string[];
26
+ pattern: RegExp;
27
+ matchToken: string;
28
+ }
29
+
30
+ const PROHIBITIVE_PATTERN = /\b(no|do not|don't|without|avoid|ban|disallow|never|must not)\b/i;
31
+
32
+ const CONSTRAINT_TEMPLATES: ConstraintTemplate[] = [
33
+ {
34
+ id: 'no_useeffect',
35
+ displayName: 'No useEffect',
36
+ triggerTokens: ['useeffect'],
37
+ pattern: /\buseEffect\s*\(/i,
38
+ matchToken: 'useeffect',
39
+ },
40
+ {
41
+ id: 'no_console_log',
42
+ displayName: 'No console.log',
43
+ triggerTokens: ['console.log', 'console log'],
44
+ pattern: /\bconsole\.log\s*\(/i,
45
+ matchToken: 'console.log',
46
+ },
47
+ {
48
+ id: 'no_debugger',
49
+ displayName: 'No debugger statements',
50
+ triggerTokens: ['debugger'],
51
+ pattern: /\bdebugger\b/i,
52
+ matchToken: 'debugger',
53
+ },
54
+ {
55
+ id: 'no_eval',
56
+ displayName: 'No eval usage',
57
+ triggerTokens: ['eval'],
58
+ pattern: /\beval\s*\(/i,
59
+ matchToken: 'eval',
60
+ },
61
+ {
62
+ id: 'no_process_env',
63
+ displayName: 'No process.env access',
64
+ triggerTokens: ['process.env', 'process env'],
65
+ pattern: /\bprocess\.env\b/i,
66
+ matchToken: 'process.env',
67
+ },
68
+ {
69
+ id: 'no_any_type',
70
+ displayName: 'No any type',
71
+ triggerTokens: [' any', 'type any', ': any', '<any>'],
72
+ pattern: /(:\s*any\b|<\s*any\s*>|\bArray<any>\b|\bPromise<any>\b)/i,
73
+ matchToken: 'any',
74
+ },
75
+ {
76
+ id: 'no_todo_fixme',
77
+ displayName: 'No TODO/FIXME markers',
78
+ triggerTokens: ['todo', 'fixme'],
79
+ pattern: /\b(TODO|FIXME)\b/i,
80
+ matchToken: 'todo/fixme',
81
+ },
82
+ ];
83
+
84
+ function splitStatements(raw: string): string[] {
85
+ return raw
86
+ .split(/[\n;]+/)
87
+ .map((part) => part.trim())
88
+ .filter(Boolean);
89
+ }
90
+
91
+ function normalizeStatement(statement: string): string {
92
+ return statement
93
+ .replace(/\s+/g, ' ')
94
+ .trim()
95
+ .toLowerCase();
96
+ }
97
+
98
+ function statementMatchesTemplate(normalizedStatement: string, template: ConstraintTemplate): boolean {
99
+ return template.triggerTokens.some((token) => normalizedStatement.includes(token));
100
+ }
101
+
102
+ function createRule(
103
+ template: ConstraintTemplate,
104
+ source: DeterministicConstraintSource,
105
+ statement: string
106
+ ): DeterministicConstraintRule {
107
+ return {
108
+ id: `${source}:${template.id}`,
109
+ source,
110
+ statement,
111
+ displayName: template.displayName,
112
+ pattern: template.pattern,
113
+ matchToken: template.matchToken,
114
+ };
115
+ }
116
+
117
+ function compileStatements(
118
+ statements: string[],
119
+ source: DeterministicConstraintSource
120
+ ): DeterministicConstraintCompilation {
121
+ const rules: DeterministicConstraintRule[] = [];
122
+ const unmatchedStatements: string[] = [];
123
+
124
+ for (const rawStatement of statements) {
125
+ const normalized = normalizeStatement(rawStatement);
126
+ if (!normalized) {
127
+ continue;
128
+ }
129
+
130
+ const requiresProhibitiveLanguage = source === 'intent';
131
+ if (requiresProhibitiveLanguage && !PROHIBITIVE_PATTERN.test(normalized)) {
132
+ continue;
133
+ }
134
+
135
+ const matches = CONSTRAINT_TEMPLATES.filter((template) => statementMatchesTemplate(normalized, template));
136
+ if (matches.length === 0) {
137
+ unmatchedStatements.push(rawStatement);
138
+ continue;
139
+ }
140
+
141
+ for (const match of matches) {
142
+ rules.push(createRule(match, source, rawStatement));
143
+ }
144
+ }
145
+
146
+ return {
147
+ rules,
148
+ unmatchedStatements,
149
+ };
150
+ }
151
+
152
+ function dedupeRules(rules: DeterministicConstraintRule[]): DeterministicConstraintRule[] {
153
+ const seen = new Set<string>();
154
+ const deduped: DeterministicConstraintRule[] = [];
155
+
156
+ for (const rule of rules) {
157
+ const key = `${rule.id}::${rule.statement.toLowerCase()}`;
158
+ if (seen.has(key)) {
159
+ continue;
160
+ }
161
+ seen.add(key);
162
+ deduped.push(rule);
163
+ }
164
+
165
+ return deduped;
166
+ }
167
+
168
+ export function compileDeterministicConstraints(
169
+ input: DeterministicConstraintCompilationInput
170
+ ): DeterministicConstraintCompilation {
171
+ const intentStatements = splitStatements(input.intentConstraints || '');
172
+ const policyStatements = (input.policyRules || [])
173
+ .map((rule) => String(rule || '').trim())
174
+ .filter(Boolean);
175
+
176
+ const compiledIntent = compileStatements(intentStatements, 'intent');
177
+ const compiledPolicy = compileStatements(policyStatements, 'policy');
178
+
179
+ return {
180
+ rules: dedupeRules([...compiledIntent.rules, ...compiledPolicy.rules]),
181
+ unmatchedStatements: [...compiledIntent.unmatchedStatements, ...compiledPolicy.unmatchedStatements],
182
+ };
183
+ }
@@ -0,0 +1,204 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import {
4
+ compileDeterministicConstraints,
5
+ buildPlanVerificationMessage,
6
+ evaluatePlanVerification,
7
+ extractPlannedFilePaths,
8
+ resolvePlanVerdict,
9
+ type PlanDiffFile,
10
+ } from './index';
11
+
12
+ const SAMPLE_PLAN = [
13
+ { path: 'src/a.ts', action: 'MODIFY' },
14
+ { path: 'src/b.ts', action: 'CREATE' },
15
+ { path: 'docs/readme.md', action: 'BLOCK' },
16
+ ];
17
+
18
+ function makeChangedFiles(paths: string[]): PlanDiffFile[] {
19
+ return paths.map((path) => ({
20
+ path,
21
+ changeType: 'modify',
22
+ added: 1,
23
+ removed: 0,
24
+ hunks: [
25
+ {
26
+ oldStart: 1,
27
+ oldLines: 0,
28
+ newStart: 1,
29
+ newLines: 1,
30
+ lines: [
31
+ {
32
+ type: 'added',
33
+ content: 'const x = 1;',
34
+ },
35
+ ],
36
+ },
37
+ ],
38
+ }));
39
+ }
40
+
41
+ test('extractPlannedFilePaths includes only CREATE/MODIFY actions', () => {
42
+ const paths = extractPlannedFilePaths(SAMPLE_PLAN);
43
+ assert.deepEqual(paths, ['src/a.ts', 'src/b.ts']);
44
+ });
45
+
46
+ test('resolvePlanVerdict reproduces legacy threshold rules', () => {
47
+ assert.equal(
48
+ resolvePlanVerdict({
49
+ bloatCount: 0,
50
+ adherenceScore: 100,
51
+ totalPlannedFiles: 2,
52
+ plannedFilesModified: 2,
53
+ constraintViolations: [],
54
+ }),
55
+ 'PASS'
56
+ );
57
+
58
+ assert.equal(
59
+ resolvePlanVerdict({
60
+ bloatCount: 1,
61
+ adherenceScore: 100,
62
+ totalPlannedFiles: 2,
63
+ plannedFilesModified: 2,
64
+ constraintViolations: [],
65
+ }),
66
+ 'WARN'
67
+ );
68
+
69
+ assert.equal(
70
+ resolvePlanVerdict({
71
+ bloatCount: 2,
72
+ adherenceScore: 40,
73
+ totalPlannedFiles: 5,
74
+ plannedFilesModified: 2,
75
+ constraintViolations: [],
76
+ }),
77
+ 'FAIL'
78
+ );
79
+
80
+ assert.equal(
81
+ resolvePlanVerdict({
82
+ bloatCount: 0,
83
+ adherenceScore: 0,
84
+ totalPlannedFiles: 0,
85
+ plannedFilesModified: 0,
86
+ constraintViolations: [],
87
+ }),
88
+ 'FAIL'
89
+ );
90
+ });
91
+
92
+ test('evaluatePlanVerification computes adherence and bloat deterministically', () => {
93
+ const result = evaluatePlanVerification({
94
+ planFiles: SAMPLE_PLAN,
95
+ changedFiles: makeChangedFiles(['src/a.ts', 'src/unplanned.ts']),
96
+ diffStats: {
97
+ totalAdded: 2,
98
+ totalRemoved: 0,
99
+ totalFiles: 2,
100
+ },
101
+ });
102
+
103
+ assert.equal(result.adherenceScore, 50);
104
+ assert.equal(result.totalPlannedFiles, 2);
105
+ assert.equal(result.plannedFilesModified, 1);
106
+ assert.equal(result.bloatCount, 1);
107
+ assert.deepEqual(result.bloatFiles, ['src/unplanned.ts']);
108
+ assert.equal(result.verdict, 'WARN');
109
+ assert.equal(result.scopeGuardPassed, false);
110
+ assert.equal(result.diffSummary.added, 2);
111
+ assert.equal(result.diffSummary.removed, 0);
112
+ });
113
+
114
+ test('evaluatePlanVerification fails on no-useEffect constraint violations', () => {
115
+ const result = evaluatePlanVerification({
116
+ planFiles: SAMPLE_PLAN,
117
+ changedFiles: [
118
+ {
119
+ path: 'src/a.ts',
120
+ changeType: 'modify',
121
+ added: 1,
122
+ removed: 0,
123
+ hunks: [
124
+ {
125
+ oldStart: 1,
126
+ oldLines: 0,
127
+ newStart: 1,
128
+ newLines: 1,
129
+ lines: [
130
+ {
131
+ type: 'added',
132
+ content: 'useEffect(() => { console.log(1); }, []);',
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ },
138
+ ],
139
+ intentConstraints: 'No useEffect',
140
+ });
141
+
142
+ assert.equal(result.verdict, 'FAIL');
143
+ assert.equal(result.constraintViolations.length, 1);
144
+ assert.match(result.message, /Constraint violation/i);
145
+ });
146
+
147
+ test('compileDeterministicConstraints builds rules from intent and policy text', () => {
148
+ const compiled = compileDeterministicConstraints({
149
+ intentConstraints: 'Do not use useEffect; avoid console.log in this change',
150
+ policyRules: ['No debugger statements', 'No process.env usage in frontend code'],
151
+ });
152
+
153
+ const ruleIds = compiled.rules.map((rule) => rule.id);
154
+ assert.ok(ruleIds.includes('intent:no_useeffect'));
155
+ assert.ok(ruleIds.includes('intent:no_console_log'));
156
+ assert.ok(ruleIds.includes('policy:no_debugger'));
157
+ assert.ok(ruleIds.includes('policy:no_process_env'));
158
+ });
159
+
160
+ test('evaluatePlanVerification enforces compiled policy rules deterministically', () => {
161
+ const result = evaluatePlanVerification({
162
+ planFiles: SAMPLE_PLAN,
163
+ changedFiles: [
164
+ {
165
+ path: 'src/a.ts',
166
+ changeType: 'modify',
167
+ added: 1,
168
+ removed: 0,
169
+ hunks: [
170
+ {
171
+ oldStart: 1,
172
+ oldLines: 0,
173
+ newStart: 1,
174
+ newLines: 1,
175
+ lines: [
176
+ {
177
+ type: 'added',
178
+ content: 'console.log("debug");',
179
+ },
180
+ ],
181
+ },
182
+ ],
183
+ },
184
+ ],
185
+ policyRules: ['No console.log in committed code'],
186
+ });
187
+
188
+ assert.equal(result.verdict, 'FAIL');
189
+ assert.equal(result.constraintViolations.length, 1);
190
+ assert.match(result.constraintViolations[0], /console\.log/i);
191
+ });
192
+
193
+ test('buildPlanVerificationMessage handles incomplete 0/0 plans', () => {
194
+ const message = buildPlanVerificationMessage({
195
+ constraintViolations: [],
196
+ totalPlannedFiles: 0,
197
+ plannedFilesModified: 0,
198
+ verdict: 'FAIL',
199
+ adherenceScore: 0,
200
+ bloatCount: 0,
201
+ });
202
+
203
+ assert.match(message, /0\/0/);
204
+ });
package/src/index.ts ADDED
@@ -0,0 +1,291 @@
1
+ export type PlanVerdict = 'PASS' | 'FAIL' | 'WARN';
2
+ export {
3
+ compileDeterministicConstraints,
4
+ type DeterministicConstraintCompilation,
5
+ type DeterministicConstraintCompilationInput,
6
+ type DeterministicConstraintRule,
7
+ type DeterministicConstraintSource,
8
+ } from './constraints';
9
+ import { compileDeterministicConstraints, type DeterministicConstraintRule } from './constraints';
10
+
11
+ export interface PlanFileScopeItem {
12
+ path: string;
13
+ action: string;
14
+ }
15
+
16
+ export interface PlanDiffLine {
17
+ type: 'context' | 'added' | 'removed';
18
+ content: string;
19
+ lineNumber?: number;
20
+ }
21
+
22
+ export interface PlanDiffHunk {
23
+ oldStart: number;
24
+ oldLines: number;
25
+ newStart: number;
26
+ newLines: number;
27
+ lines: PlanDiffLine[];
28
+ }
29
+
30
+ export interface PlanDiffFile {
31
+ path: string;
32
+ oldPath?: string;
33
+ changeType: 'add' | 'delete' | 'modify' | 'rename';
34
+ added: number;
35
+ removed: number;
36
+ hunks?: PlanDiffHunk[];
37
+ }
38
+
39
+ export interface PlanDiffStats {
40
+ totalAdded: number;
41
+ totalRemoved: number;
42
+ totalFiles: number;
43
+ }
44
+
45
+ export interface PlanVerificationInput {
46
+ planFiles: PlanFileScopeItem[];
47
+ changedFiles: PlanDiffFile[];
48
+ diffStats?: PlanDiffStats;
49
+ intentConstraints?: string;
50
+ policyRules?: string[];
51
+ extraConstraintRules?: DeterministicConstraintRule[];
52
+ }
53
+
54
+ export interface PlanDiffSummary {
55
+ added: number;
56
+ removed: number;
57
+ files: Array<{
58
+ path: string;
59
+ oldPath?: string;
60
+ changeType: string;
61
+ added: number;
62
+ removed: number;
63
+ hunks: PlanDiffHunk[];
64
+ }>;
65
+ bloatFiles: string[];
66
+ plannedFilesModified: number;
67
+ totalPlannedFiles: number;
68
+ }
69
+
70
+ export interface PlanVerificationResult {
71
+ adherenceScore: number;
72
+ bloatCount: number;
73
+ bloatFiles: string[];
74
+ plannedFilesModified: number;
75
+ totalPlannedFiles: number;
76
+ scopeGuardPassed: boolean;
77
+ constraintViolations: string[];
78
+ verdict: PlanVerdict;
79
+ diffSummary: PlanDiffSummary;
80
+ message: string;
81
+ }
82
+
83
+ function normalizeRepoPath(pathValue: string): string {
84
+ return pathValue.replace(/\\/g, '/').replace(/^\.\//, '').trim();
85
+ }
86
+
87
+ export function extractPlannedFilePaths(planFiles: PlanFileScopeItem[]): string[] {
88
+ const plannedFilePaths = new Set<string>();
89
+
90
+ for (const file of planFiles) {
91
+ const normalizedPath = normalizeRepoPath(file.path);
92
+ if (!normalizedPath) {
93
+ continue;
94
+ }
95
+
96
+ if (file.action === 'MODIFY' || file.action === 'CREATE') {
97
+ plannedFilePaths.add(normalizedPath);
98
+ }
99
+ }
100
+
101
+ return Array.from(plannedFilePaths);
102
+ }
103
+
104
+ function detectConstraintViolations(
105
+ intentConstraints: string | undefined,
106
+ policyRules: string[] | undefined,
107
+ extraConstraintRules: DeterministicConstraintRule[] | undefined,
108
+ changedFiles: PlanDiffFile[]
109
+ ): string[] {
110
+ const compiled = compileDeterministicConstraints({
111
+ intentConstraints,
112
+ policyRules,
113
+ });
114
+
115
+ const rules = [
116
+ ...compiled.rules,
117
+ ...(extraConstraintRules || []),
118
+ ];
119
+
120
+ if (rules.length === 0) {
121
+ return [];
122
+ }
123
+
124
+ const violations: string[] = [];
125
+ const seenViolations = new Set<string>();
126
+
127
+ for (const rule of rules) {
128
+ for (const file of changedFiles) {
129
+ if (!file.hunks || file.hunks.length === 0) {
130
+ continue;
131
+ }
132
+ for (const hunk of file.hunks) {
133
+ for (const line of hunk.lines) {
134
+ if (line.type !== 'added') {
135
+ continue;
136
+ }
137
+ if (!rule.pattern.test(line.content)) {
138
+ continue;
139
+ }
140
+ const violation = `${rule.matchToken} found in ${file.path} (violates constraint: \"${rule.displayName}\")`;
141
+ if (seenViolations.has(violation)) {
142
+ continue;
143
+ }
144
+ seenViolations.add(violation);
145
+ violations.push(violation);
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ return violations;
152
+ }
153
+
154
+ export function resolvePlanVerdict(input: {
155
+ bloatCount: number;
156
+ adherenceScore: number;
157
+ totalPlannedFiles: number;
158
+ plannedFilesModified: number;
159
+ constraintViolations: string[];
160
+ }): PlanVerdict {
161
+ if (input.constraintViolations.length > 0) {
162
+ return 'FAIL';
163
+ }
164
+
165
+ if (input.totalPlannedFiles === 0 && input.plannedFilesModified === 0) {
166
+ // 0/0 is treated as incomplete, not perfect adherence.
167
+ return 'FAIL';
168
+ }
169
+
170
+ if (input.bloatCount > 0 && input.adherenceScore < 50) {
171
+ return 'FAIL';
172
+ }
173
+
174
+ if (input.bloatCount > 0 || input.adherenceScore < 80) {
175
+ return 'WARN';
176
+ }
177
+
178
+ return 'PASS';
179
+ }
180
+
181
+ export function buildPlanVerificationMessage(result: Pick<
182
+ PlanVerificationResult,
183
+ 'constraintViolations' | 'totalPlannedFiles' | 'plannedFilesModified' | 'verdict' | 'adherenceScore' | 'bloatCount'
184
+ >): string {
185
+ if (result.constraintViolations.length > 0) {
186
+ return `❌ Constraint violation: ${result.constraintViolations[0]}${result.constraintViolations.length > 1 ? ` (+${result.constraintViolations.length - 1} more)` : ''}`;
187
+ }
188
+
189
+ if (result.totalPlannedFiles === 0 && result.plannedFilesModified === 0) {
190
+ return '❌ Incomplete: No planned files to verify (0/0). Plan may be malformed.';
191
+ }
192
+
193
+ if (result.verdict === 'PASS') {
194
+ return `✅ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)`;
195
+ }
196
+
197
+ if (result.verdict === 'WARN') {
198
+ return `⚠️ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)${result.bloatCount > 0 ? `, ${result.bloatCount} unexpected file(s) changed` : ''}`;
199
+ }
200
+
201
+ return `❌ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified), ${result.bloatCount} unexpected file(s) changed`;
202
+ }
203
+
204
+ export function evaluatePlanVerification(input: PlanVerificationInput): PlanVerificationResult {
205
+ const plannedFilePaths = extractPlannedFilePaths(input.planFiles);
206
+ const plannedSet = new Set(plannedFilePaths);
207
+
208
+ const normalizedChangedFiles = input.changedFiles.map((file) => ({
209
+ ...file,
210
+ path: normalizeRepoPath(file.path),
211
+ oldPath: file.oldPath ? normalizeRepoPath(file.oldPath) : undefined,
212
+ hunks: file.hunks || [],
213
+ }));
214
+
215
+ const changedFilePaths = Array.from(
216
+ new Set(
217
+ normalizedChangedFiles
218
+ .map((file) => file.path)
219
+ .filter(Boolean)
220
+ )
221
+ );
222
+ const changedSet = new Set(changedFilePaths);
223
+
224
+ const bloatFiles = changedFilePaths.filter((pathValue) => !plannedSet.has(pathValue));
225
+ const bloatCount = bloatFiles.length;
226
+
227
+ const totalPlannedFiles = plannedSet.size;
228
+ const plannedFilesModified = plannedFilePaths.filter((pathValue) => changedSet.has(pathValue)).length;
229
+ const adherenceScore = totalPlannedFiles > 0
230
+ ? Math.round((plannedFilesModified / totalPlannedFiles) * 100)
231
+ : 0;
232
+
233
+ const constraintViolations = detectConstraintViolations(
234
+ input.intentConstraints,
235
+ input.policyRules,
236
+ input.extraConstraintRules,
237
+ normalizedChangedFiles
238
+ );
239
+ const verdict = resolvePlanVerdict({
240
+ bloatCount,
241
+ adherenceScore,
242
+ totalPlannedFiles,
243
+ plannedFilesModified,
244
+ constraintViolations,
245
+ });
246
+
247
+ const totalAdded = input.diffStats
248
+ ? input.diffStats.totalAdded
249
+ : normalizedChangedFiles.reduce((sum, file) => sum + file.added, 0);
250
+ const totalRemoved = input.diffStats
251
+ ? input.diffStats.totalRemoved
252
+ : normalizedChangedFiles.reduce((sum, file) => sum + file.removed, 0);
253
+
254
+ const diffSummary: PlanDiffSummary = {
255
+ added: totalAdded,
256
+ removed: totalRemoved,
257
+ files: normalizedChangedFiles.map((file) => ({
258
+ path: file.path,
259
+ oldPath: file.oldPath,
260
+ changeType: file.changeType,
261
+ added: file.added,
262
+ removed: file.removed,
263
+ hunks: file.hunks || [],
264
+ })),
265
+ bloatFiles,
266
+ plannedFilesModified,
267
+ totalPlannedFiles,
268
+ };
269
+
270
+ const message = buildPlanVerificationMessage({
271
+ constraintViolations,
272
+ totalPlannedFiles,
273
+ plannedFilesModified,
274
+ verdict,
275
+ adherenceScore,
276
+ bloatCount,
277
+ });
278
+
279
+ return {
280
+ adherenceScore,
281
+ bloatCount,
282
+ bloatFiles,
283
+ plannedFilesModified,
284
+ totalPlannedFiles,
285
+ scopeGuardPassed: bloatCount === 0,
286
+ constraintViolations,
287
+ verdict,
288
+ diffSummary,
289
+ message,
290
+ };
291
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
19
+ }