@neurcode-ai/governance-runtime 0.1.3 → 0.1.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.
Files changed (50) hide show
  1. package/LICENSE +201 -0
  2. package/dist/admission-provenance.d.ts +111 -0
  3. package/dist/admission-provenance.d.ts.map +1 -0
  4. package/dist/admission-provenance.js +735 -0
  5. package/dist/admission-provenance.js.map +1 -0
  6. package/dist/agent-guard-posture.d.ts +40 -0
  7. package/dist/agent-guard-posture.d.ts.map +1 -0
  8. package/dist/agent-guard-posture.js +117 -0
  9. package/dist/agent-guard-posture.js.map +1 -0
  10. package/dist/agent-invocation-observability.d.ts +47 -0
  11. package/dist/agent-invocation-observability.d.ts.map +1 -0
  12. package/dist/agent-invocation-observability.js +229 -0
  13. package/dist/agent-invocation-observability.js.map +1 -0
  14. package/dist/agent-plan.d.ts +119 -0
  15. package/dist/agent-plan.d.ts.map +1 -0
  16. package/dist/agent-plan.js +565 -0
  17. package/dist/agent-plan.js.map +1 -0
  18. package/dist/agent-runtime-adapter.d.ts +69 -0
  19. package/dist/agent-runtime-adapter.d.ts.map +1 -0
  20. package/dist/agent-runtime-adapter.js +274 -0
  21. package/dist/agent-runtime-adapter.js.map +1 -0
  22. package/dist/ai-change-record.d.ts +185 -0
  23. package/dist/ai-change-record.d.ts.map +1 -0
  24. package/dist/ai-change-record.js +580 -0
  25. package/dist/ai-change-record.js.map +1 -0
  26. package/dist/architecture-graph.d.ts +153 -0
  27. package/dist/architecture-graph.d.ts.map +1 -0
  28. package/dist/architecture-graph.js +646 -0
  29. package/dist/architecture-graph.js.map +1 -0
  30. package/dist/architecture-obligations.d.ts +153 -0
  31. package/dist/architecture-obligations.d.ts.map +1 -0
  32. package/dist/architecture-obligations.js +505 -0
  33. package/dist/architecture-obligations.js.map +1 -0
  34. package/dist/index.d.ts +10 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +103 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/profile.d.ts +159 -0
  39. package/dist/profile.d.ts.map +1 -0
  40. package/dist/profile.js +611 -0
  41. package/dist/profile.js.map +1 -0
  42. package/dist/session.d.ts +428 -0
  43. package/dist/session.d.ts.map +1 -0
  44. package/dist/session.js +2052 -0
  45. package/dist/session.js.map +1 -0
  46. package/package.json +19 -8
  47. package/src/constraints.ts +0 -828
  48. package/src/index.test.ts +0 -502
  49. package/src/index.ts +0 -463
  50. package/tsconfig.json +0 -19
package/src/index.ts DELETED
@@ -1,463 +0,0 @@
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
- fileContents?: Record<string, string>;
53
- }
54
-
55
- export interface PlanDiffSummary {
56
- added: number;
57
- removed: number;
58
- files: Array<{
59
- path: string;
60
- oldPath?: string;
61
- changeType: string;
62
- added: number;
63
- removed: number;
64
- hunks: PlanDiffHunk[];
65
- }>;
66
- bloatFiles: string[];
67
- plannedFilesModified: number;
68
- totalPlannedFiles: number;
69
- }
70
-
71
- export interface PlanVerificationResult {
72
- adherenceScore: number;
73
- bloatCount: number;
74
- bloatFiles: string[];
75
- plannedFilesModified: number;
76
- totalPlannedFiles: number;
77
- scopeGuardPassed: boolean;
78
- constraintViolations: string[];
79
- verdict: PlanVerdict;
80
- diffSummary: PlanDiffSummary;
81
- message: string;
82
- }
83
-
84
- function normalizeRepoPath(pathValue: string): string {
85
- return pathValue.replace(/\\/g, '/').replace(/^\.\//, '').trim();
86
- }
87
-
88
- export function extractPlannedFilePaths(planFiles: PlanFileScopeItem[]): string[] {
89
- const plannedFilePaths = new Set<string>();
90
-
91
- for (const file of planFiles) {
92
- const normalizedPath = normalizeRepoPath(file.path);
93
- if (!normalizedPath) {
94
- continue;
95
- }
96
-
97
- if (file.action === 'MODIFY' || file.action === 'CREATE') {
98
- plannedFilePaths.add(normalizedPath);
99
- }
100
- }
101
-
102
- return Array.from(plannedFilePaths);
103
- }
104
-
105
- function detectConstraintViolations(
106
- intentConstraints: string | undefined,
107
- policyRules: string[] | undefined,
108
- extraConstraintRules: DeterministicConstraintRule[] | undefined,
109
- changedFiles: PlanDiffFile[],
110
- fileContents?: Record<string, string>
111
- ): string[] {
112
- const compiled = compileDeterministicConstraints({
113
- intentConstraints,
114
- policyRules,
115
- });
116
-
117
- const rules = [
118
- ...compiled.rules,
119
- ...(extraConstraintRules || []),
120
- ];
121
-
122
- if (rules.length === 0) {
123
- return [];
124
- }
125
-
126
- const violations: string[] = [];
127
- const seenViolations = new Set<string>();
128
- const normalizedFileContents: Record<string, string> = {};
129
- for (const [path, content] of Object.entries(fileContents || {})) {
130
- normalizedFileContents[normalizeRepoPath(path)] = content;
131
- }
132
-
133
- const pathMatchesRule = (rule: DeterministicConstraintRule, filePath: string): boolean => {
134
- const include = rule.pathIncludes || [];
135
- const exclude = rule.pathExcludes || [];
136
- if (include.length > 0 && !include.some((pattern) => pattern.test(filePath))) {
137
- return false;
138
- }
139
- if (exclude.length > 0 && exclude.some((pattern) => pattern.test(filePath))) {
140
- return false;
141
- }
142
- return true;
143
- };
144
-
145
- const countMatches = (pattern: RegExp, input: string): number => {
146
- if (!input) return 0;
147
- const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
148
- const re = new RegExp(pattern.source, flags);
149
- let total = 0;
150
- for (const _match of input.matchAll(re)) {
151
- total += 1;
152
- }
153
- return total;
154
- };
155
-
156
- const addedLinesByPath = new Map<string, string>();
157
- for (const file of changedFiles) {
158
- const addedLines = (file.hunks || []).flatMap((hunk) =>
159
- hunk.lines
160
- .filter((line) => line.type === 'added')
161
- .map((line) => line.content)
162
- );
163
- addedLinesByPath.set(file.path, addedLines.join('\n'));
164
- }
165
-
166
- const candidateRepoPaths = new Set<string>([
167
- ...changedFiles.map((file) => file.path),
168
- ...Object.keys(normalizedFileContents),
169
- ]);
170
-
171
- for (const rule of rules) {
172
- if (rule.evaluationMode === 'signature_delta') {
173
- for (const file of changedFiles) {
174
- if (!pathMatchesRule(rule, file.path)) {
175
- continue;
176
- }
177
- const addedSignatureLines = (file.hunks || []).flatMap((hunk) =>
178
- hunk.lines
179
- .filter((line) => line.type === 'added' && new RegExp(rule.pattern.source, rule.pattern.flags).test(line.content))
180
- .map((line) => line.content)
181
- );
182
- const removedSignatureLines = (file.hunks || []).flatMap((hunk) =>
183
- hunk.lines
184
- .filter((line) => line.type === 'removed' && new RegExp(rule.pattern.source, rule.pattern.flags).test(line.content))
185
- .map((line) => line.content)
186
- );
187
- const deltaCount = addedSignatureLines.length + removedSignatureLines.length;
188
- if (deltaCount === 0) {
189
- continue;
190
- }
191
- const violation =
192
- `Exported API signature delta detected in ${file.path} ` +
193
- `(added ${addedSignatureLines.length}, removed ${removedSignatureLines.length}, violates constraint: "${rule.displayName}")`;
194
- if (!seenViolations.has(violation)) {
195
- seenViolations.add(violation);
196
- violations.push(violation);
197
- }
198
- }
199
- continue;
200
- }
201
-
202
- const hasMatchBounds =
203
- (typeof rule.maxMatchesPerFile === 'number' && Number.isFinite(rule.maxMatchesPerFile))
204
- || (typeof rule.minMatchesPerFile === 'number' && Number.isFinite(rule.minMatchesPerFile));
205
-
206
- if (rule.evaluationScope === 'repo' && hasMatchBounds) {
207
- let repoMatchCount = 0;
208
- let scannedPaths = 0;
209
- for (const filePath of candidateRepoPaths) {
210
- if (!pathMatchesRule(rule, filePath)) {
211
- continue;
212
- }
213
- const fullContent = normalizedFileContents[filePath];
214
- const addedFallback = addedLinesByPath.get(filePath) || '';
215
- const haystack =
216
- rule.evaluationMode === 'full_file' && typeof fullContent === 'string'
217
- ? fullContent
218
- : addedFallback;
219
- if (!haystack) {
220
- continue;
221
- }
222
- scannedPaths += 1;
223
- repoMatchCount += countMatches(rule.pattern, haystack);
224
- }
225
-
226
- if (
227
- typeof rule.maxMatchesPerFile === 'number'
228
- && Number.isFinite(rule.maxMatchesPerFile)
229
- && repoMatchCount > rule.maxMatchesPerFile
230
- ) {
231
- const violation =
232
- `${rule.matchToken} matched ${repoMatchCount} times across repository scope ` +
233
- `(limit ${rule.maxMatchesPerFile}, scanned ${scannedPaths} file(s), violates constraint: "${rule.displayName}")`;
234
- if (!seenViolations.has(violation)) {
235
- seenViolations.add(violation);
236
- violations.push(violation);
237
- }
238
- }
239
-
240
- if (
241
- typeof rule.minMatchesPerFile === 'number'
242
- && Number.isFinite(rule.minMatchesPerFile)
243
- && repoMatchCount < rule.minMatchesPerFile
244
- ) {
245
- const violation =
246
- `${rule.matchToken} matched ${repoMatchCount} times across repository scope ` +
247
- `(minimum ${rule.minMatchesPerFile}, scanned ${scannedPaths} file(s), violates constraint: "${rule.displayName}")`;
248
- if (!seenViolations.has(violation)) {
249
- seenViolations.add(violation);
250
- violations.push(violation);
251
- }
252
- }
253
- continue;
254
- }
255
-
256
- for (const file of changedFiles) {
257
- if (!pathMatchesRule(rule, file.path)) {
258
- continue;
259
- }
260
- if (!file.hunks || file.hunks.length === 0) {
261
- continue;
262
- }
263
-
264
- const addedLines = addedLinesByPath.get(file.path) || '';
265
-
266
- if (hasMatchBounds) {
267
- const fullContent = normalizedFileContents[file.path];
268
- const haystack =
269
- rule.evaluationMode === 'full_file' && typeof fullContent === 'string'
270
- ? fullContent
271
- : addedLines;
272
- const matchCount = countMatches(rule.pattern, haystack);
273
- if (
274
- typeof rule.maxMatchesPerFile === 'number'
275
- && Number.isFinite(rule.maxMatchesPerFile)
276
- && matchCount > rule.maxMatchesPerFile
277
- ) {
278
- const violation =
279
- `${rule.matchToken} matched ${matchCount} times in ${file.path} ` +
280
- `(limit ${rule.maxMatchesPerFile}, violates constraint: "${rule.displayName}")`;
281
- if (!seenViolations.has(violation)) {
282
- seenViolations.add(violation);
283
- violations.push(violation);
284
- }
285
- }
286
- if (
287
- typeof rule.minMatchesPerFile === 'number'
288
- && Number.isFinite(rule.minMatchesPerFile)
289
- && matchCount < rule.minMatchesPerFile
290
- ) {
291
- const violation =
292
- `${rule.matchToken} matched ${matchCount} times in ${file.path} ` +
293
- `(minimum ${rule.minMatchesPerFile}, violates constraint: "${rule.displayName}")`;
294
- if (!seenViolations.has(violation)) {
295
- seenViolations.add(violation);
296
- violations.push(violation);
297
- }
298
- }
299
- continue;
300
- }
301
-
302
- for (const hunk of file.hunks) {
303
- for (const line of hunk.lines) {
304
- if (line.type !== 'added') {
305
- continue;
306
- }
307
- const pattern = new RegExp(rule.pattern.source, rule.pattern.flags);
308
- if (!pattern.test(line.content)) {
309
- continue;
310
- }
311
- const violation = `${rule.matchToken} found in ${file.path} (violates constraint: \"${rule.displayName}\")`;
312
- if (seenViolations.has(violation)) {
313
- continue;
314
- }
315
- seenViolations.add(violation);
316
- violations.push(violation);
317
- }
318
- }
319
- }
320
- }
321
-
322
- return violations;
323
- }
324
-
325
- export function resolvePlanVerdict(input: {
326
- bloatCount: number;
327
- adherenceScore: number;
328
- totalPlannedFiles: number;
329
- plannedFilesModified: number;
330
- constraintViolations: string[];
331
- }): PlanVerdict {
332
- if (input.constraintViolations.length > 0) {
333
- return 'FAIL';
334
- }
335
-
336
- if (input.totalPlannedFiles === 0 && input.plannedFilesModified === 0) {
337
- // 0/0 is treated as incomplete, not perfect adherence.
338
- return 'FAIL';
339
- }
340
-
341
- if (input.bloatCount > 0 && input.adherenceScore < 50) {
342
- return 'FAIL';
343
- }
344
-
345
- if (input.bloatCount > 0 || input.adherenceScore < 80) {
346
- return 'WARN';
347
- }
348
-
349
- return 'PASS';
350
- }
351
-
352
- export function buildPlanVerificationMessage(result: Pick<
353
- PlanVerificationResult,
354
- 'constraintViolations' | 'totalPlannedFiles' | 'plannedFilesModified' | 'verdict' | 'adherenceScore' | 'bloatCount'
355
- >): string {
356
- if (result.constraintViolations.length > 0) {
357
- return `❌ Constraint violation: ${result.constraintViolations[0]}${result.constraintViolations.length > 1 ? ` (+${result.constraintViolations.length - 1} more)` : ''}`;
358
- }
359
-
360
- if (result.totalPlannedFiles === 0 && result.plannedFilesModified === 0) {
361
- return '❌ Incomplete: No planned files to verify (0/0). Plan may be malformed.';
362
- }
363
-
364
- if (result.verdict === 'PASS') {
365
- return `✅ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)`;
366
- }
367
-
368
- if (result.verdict === 'WARN') {
369
- return `⚠️ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified)${result.bloatCount > 0 ? `, ${result.bloatCount} unexpected file(s) changed` : ''}`;
370
- }
371
-
372
- return `❌ Plan adherence: ${result.adherenceScore}% (${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified), ${result.bloatCount} unexpected file(s) changed`;
373
- }
374
-
375
- export function evaluatePlanVerification(input: PlanVerificationInput): PlanVerificationResult {
376
- const plannedFilePaths = extractPlannedFilePaths(input.planFiles);
377
- const plannedSet = new Set(plannedFilePaths);
378
-
379
- const normalizedChangedFiles = input.changedFiles.map((file) => ({
380
- ...file,
381
- path: normalizeRepoPath(file.path),
382
- oldPath: file.oldPath ? normalizeRepoPath(file.oldPath) : undefined,
383
- hunks: file.hunks || [],
384
- }));
385
-
386
- const changedFilePaths = Array.from(
387
- new Set(
388
- normalizedChangedFiles
389
- .map((file) => file.path)
390
- .filter(Boolean)
391
- )
392
- );
393
- const changedSet = new Set(changedFilePaths);
394
-
395
- const bloatFiles = changedFilePaths.filter((pathValue) => !plannedSet.has(pathValue));
396
- const bloatCount = bloatFiles.length;
397
-
398
- const totalPlannedFiles = plannedSet.size;
399
- const plannedFilesModified = plannedFilePaths.filter((pathValue) => changedSet.has(pathValue)).length;
400
- const adherenceScore = totalPlannedFiles > 0
401
- ? Math.round((plannedFilesModified / totalPlannedFiles) * 100)
402
- : 0;
403
-
404
- const constraintViolations = detectConstraintViolations(
405
- input.intentConstraints,
406
- input.policyRules,
407
- input.extraConstraintRules,
408
- normalizedChangedFiles,
409
- input.fileContents
410
- );
411
- const verdict = resolvePlanVerdict({
412
- bloatCount,
413
- adherenceScore,
414
- totalPlannedFiles,
415
- plannedFilesModified,
416
- constraintViolations,
417
- });
418
-
419
- const totalAdded = input.diffStats
420
- ? input.diffStats.totalAdded
421
- : normalizedChangedFiles.reduce((sum, file) => sum + file.added, 0);
422
- const totalRemoved = input.diffStats
423
- ? input.diffStats.totalRemoved
424
- : normalizedChangedFiles.reduce((sum, file) => sum + file.removed, 0);
425
-
426
- const diffSummary: PlanDiffSummary = {
427
- added: totalAdded,
428
- removed: totalRemoved,
429
- files: normalizedChangedFiles.map((file) => ({
430
- path: file.path,
431
- oldPath: file.oldPath,
432
- changeType: file.changeType,
433
- added: file.added,
434
- removed: file.removed,
435
- hunks: file.hunks || [],
436
- })),
437
- bloatFiles,
438
- plannedFilesModified,
439
- totalPlannedFiles,
440
- };
441
-
442
- const message = buildPlanVerificationMessage({
443
- constraintViolations,
444
- totalPlannedFiles,
445
- plannedFilesModified,
446
- verdict,
447
- adherenceScore,
448
- bloatCount,
449
- });
450
-
451
- return {
452
- adherenceScore,
453
- bloatCount,
454
- bloatFiles,
455
- plannedFilesModified,
456
- totalPlannedFiles,
457
- scopeGuardPassed: bloatCount === 0,
458
- constraintViolations,
459
- verdict,
460
- diffSummary,
461
- message,
462
- };
463
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
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
- }