@behavioral-contracts/verify-cli 1.1.0 → 1.2.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.
Files changed (47) hide show
  1. package/dist/ai-prompt-generator.d.ts +16 -0
  2. package/dist/ai-prompt-generator.d.ts.map +1 -0
  3. package/dist/ai-prompt-generator.js +481 -0
  4. package/dist/ai-prompt-generator.js.map +1 -0
  5. package/dist/analyzer.d.ts +41 -0
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/analyzer.js +93 -1
  8. package/dist/analyzer.js.map +1 -1
  9. package/dist/analyzers/event-listener-analyzer.d.ts +39 -4
  10. package/dist/analyzers/event-listener-analyzer.d.ts.map +1 -1
  11. package/dist/analyzers/event-listener-analyzer.js +123 -10
  12. package/dist/analyzers/event-listener-analyzer.js.map +1 -1
  13. package/dist/cli/suppressions.d.ts +9 -0
  14. package/dist/cli/suppressions.d.ts.map +1 -0
  15. package/dist/cli/suppressions.js +242 -0
  16. package/dist/cli/suppressions.js.map +1 -0
  17. package/dist/index.js +81 -38
  18. package/dist/index.js.map +1 -1
  19. package/dist/suppressions/config-loader.d.ts +47 -0
  20. package/dist/suppressions/config-loader.d.ts.map +1 -0
  21. package/dist/suppressions/config-loader.js +214 -0
  22. package/dist/suppressions/config-loader.js.map +1 -0
  23. package/dist/suppressions/dead-suppression-detector.d.ts +45 -0
  24. package/dist/suppressions/dead-suppression-detector.d.ts.map +1 -0
  25. package/dist/suppressions/dead-suppression-detector.js +101 -0
  26. package/dist/suppressions/dead-suppression-detector.js.map +1 -0
  27. package/dist/suppressions/index.d.ts +12 -0
  28. package/dist/suppressions/index.d.ts.map +1 -0
  29. package/dist/suppressions/index.js +18 -0
  30. package/dist/suppressions/index.js.map +1 -0
  31. package/dist/suppressions/manifest.d.ts +114 -0
  32. package/dist/suppressions/manifest.d.ts.map +1 -0
  33. package/dist/suppressions/manifest.js +281 -0
  34. package/dist/suppressions/manifest.js.map +1 -0
  35. package/dist/suppressions/matcher.d.ts +73 -0
  36. package/dist/suppressions/matcher.d.ts.map +1 -0
  37. package/dist/suppressions/matcher.js +163 -0
  38. package/dist/suppressions/matcher.js.map +1 -0
  39. package/dist/suppressions/parser.d.ts +59 -0
  40. package/dist/suppressions/parser.d.ts.map +1 -0
  41. package/dist/suppressions/parser.js +156 -0
  42. package/dist/suppressions/parser.js.map +1 -0
  43. package/dist/suppressions/types.d.ts +121 -0
  44. package/dist/suppressions/types.d.ts.map +1 -0
  45. package/dist/suppressions/types.js +8 -0
  46. package/dist/suppressions/types.js.map +1 -0
  47. package/package.json +4 -2
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Suppression Matcher
3
+ *
4
+ * Combines inline comments and config rules to check if violations are suppressed
5
+ */
6
+ import * as path from 'path';
7
+ import { getSuppressionForLine, suppressionMatches } from './parser.js';
8
+ import { loadConfigSync, findMatchingRules } from './config-loader.js';
9
+ import { loadManifestSync, saveManifestSync, createSuppression, upsertSuppression } from './manifest.js';
10
+ /**
11
+ * Check if a violation is suppressed
12
+ *
13
+ * Checks both inline comments and config file rules.
14
+ * Optionally updates manifest with metadata.
15
+ *
16
+ * @param options - Check options
17
+ * @returns Suppression check result
18
+ */
19
+ export function checkSuppression(options) {
20
+ const { projectRoot, sourceFile, line, column, packageName, postconditionId, analyzerVersion, updateManifest = true } = options;
21
+ // Get relative file path from project root
22
+ const absoluteFilePath = sourceFile.fileName;
23
+ const relativeFilePath = path.relative(projectRoot, absoluteFilePath);
24
+ // Check inline comment suppression
25
+ const inlineSuppress = getSuppressionForLine(sourceFile, line);
26
+ if (inlineSuppress && suppressionMatches(inlineSuppress, packageName, postconditionId)) {
27
+ // Suppressed by inline comment
28
+ if (updateManifest) {
29
+ updateManifestWithSuppression({
30
+ projectRoot,
31
+ file: relativeFilePath,
32
+ line,
33
+ column,
34
+ packageName,
35
+ postconditionId,
36
+ reason: inlineSuppress.reason,
37
+ suppressedBy: 'inline-comment',
38
+ analyzerVersion
39
+ });
40
+ }
41
+ return {
42
+ suppressed: true,
43
+ source: 'inline-comment',
44
+ originalSource: inlineSuppress
45
+ };
46
+ }
47
+ // Check config file suppression
48
+ const config = loadConfigSync(projectRoot);
49
+ const matchingRules = findMatchingRules(config, relativeFilePath, packageName, postconditionId);
50
+ if (matchingRules.length > 0) {
51
+ // Suppressed by config file
52
+ const rule = matchingRules[0]; // Use first matching rule
53
+ if (updateManifest) {
54
+ updateManifestWithSuppression({
55
+ projectRoot,
56
+ file: relativeFilePath,
57
+ line,
58
+ column,
59
+ packageName,
60
+ postconditionId,
61
+ reason: rule.reason,
62
+ suppressedBy: 'config-file',
63
+ analyzerVersion
64
+ });
65
+ }
66
+ return {
67
+ suppressed: true,
68
+ matchedSuppression: rule,
69
+ source: 'config-file',
70
+ originalSource: rule
71
+ };
72
+ }
73
+ // Not suppressed
74
+ return {
75
+ suppressed: false
76
+ };
77
+ }
78
+ /**
79
+ * Update manifest with suppression metadata
80
+ *
81
+ * Creates or updates a suppression entry in the manifest.
82
+ *
83
+ * @param options - Suppression options
84
+ */
85
+ function updateManifestWithSuppression(options) {
86
+ try {
87
+ const manifest = loadManifestSync(options.projectRoot);
88
+ const suppression = createSuppression({
89
+ file: options.file,
90
+ line: options.line,
91
+ column: options.column,
92
+ packageName: options.packageName,
93
+ postconditionId: options.postconditionId,
94
+ reason: options.reason,
95
+ suppressedBy: options.suppressedBy,
96
+ analyzerVersion: options.analyzerVersion
97
+ });
98
+ // Update last checked time
99
+ suppression.lastChecked = new Date().toISOString();
100
+ suppression.stillViolates = true; // Confirmed to still violate
101
+ upsertSuppression(manifest, suppression);
102
+ saveManifestSync(manifest);
103
+ }
104
+ catch (error) {
105
+ // Don't fail the analysis if manifest update fails
106
+ console.warn(`Warning: Failed to update suppression manifest: ${error instanceof Error ? error.message : String(error)}`);
107
+ }
108
+ }
109
+ /**
110
+ * Batch check suppressions for multiple violations
111
+ *
112
+ * @param violations - Array of violations to check
113
+ * @param projectRoot - Project root directory
114
+ * @param analyzerVersion - Current analyzer version
115
+ * @returns Map of violation ID to suppression result
116
+ */
117
+ export function batchCheckSuppressions(violations, projectRoot, analyzerVersion) {
118
+ const results = new Map();
119
+ for (const violation of violations) {
120
+ const result = checkSuppression({
121
+ projectRoot,
122
+ sourceFile: violation.sourceFile,
123
+ line: violation.line,
124
+ column: violation.column,
125
+ packageName: violation.packageName,
126
+ postconditionId: violation.postconditionId,
127
+ analyzerVersion,
128
+ updateManifest: true
129
+ });
130
+ results.set(violation.id, result);
131
+ }
132
+ return results;
133
+ }
134
+ /**
135
+ * Get suppression statistics
136
+ *
137
+ * @param projectRoot - Project root directory
138
+ * @returns Statistics object
139
+ */
140
+ export function getSuppressionStats(projectRoot) {
141
+ const manifest = loadManifestSync(projectRoot);
142
+ const active = manifest.suppressions.filter(s => s.stillViolates);
143
+ const dead = manifest.suppressions.filter(s => !s.stillViolates);
144
+ const bySource = {
145
+ inlineComment: manifest.suppressions.filter(s => s.suppressedBy === 'inline-comment').length,
146
+ configFile: manifest.suppressions.filter(s => s.suppressedBy === 'config-file').length,
147
+ aiAgent: manifest.suppressions.filter(s => s.suppressedBy === 'ai-agent').length,
148
+ cli: manifest.suppressions.filter(s => s.suppressedBy === 'cli').length
149
+ };
150
+ const byPackage = new Map();
151
+ manifest.suppressions.forEach(s => {
152
+ const count = byPackage.get(s.package) || 0;
153
+ byPackage.set(s.package, count + 1);
154
+ });
155
+ return {
156
+ totalSuppressions: manifest.suppressions.length,
157
+ activeSuppressions: active.length,
158
+ deadSuppressions: dead.length,
159
+ bySource,
160
+ byPackage
161
+ };
162
+ }
163
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/suppressions/matcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAgCzG;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,MAAM,EACJ,WAAW,EACX,UAAU,EACV,IAAI,EACJ,MAAM,EACN,WAAW,EACX,eAAe,EACf,eAAe,EACf,cAAc,GAAG,IAAI,EACtB,GAAG,OAAO,CAAC;IAEZ,2CAA2C;IAC3C,MAAM,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC;IAC7C,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IAEtE,mCAAmC;IACnC,MAAM,cAAc,GAAG,qBAAqB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAE/D,IAAI,cAAc,IAAI,kBAAkB,CAAC,cAAc,EAAE,WAAW,EAAE,eAAe,CAAC,EAAE,CAAC;QACvF,+BAA+B;QAC/B,IAAI,cAAc,EAAE,CAAC;YACnB,6BAA6B,CAAC;gBAC5B,WAAW;gBACX,IAAI,EAAE,gBAAgB;gBACtB,IAAI;gBACJ,MAAM;gBACN,WAAW;gBACX,eAAe;gBACf,MAAM,EAAE,cAAc,CAAC,MAAM;gBAC7B,YAAY,EAAE,gBAAgB;gBAC9B,eAAe;aAChB,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,gBAAgB;YACxB,cAAc,EAAE,cAAc;SAC/B,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,MAAM,MAAM,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,iBAAiB,CACrC,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,eAAe,CAChB,CAAC;IAEF,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,4BAA4B;QAC5B,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B;QAEzD,IAAI,cAAc,EAAE,CAAC;YACnB,6BAA6B,CAAC;gBAC5B,WAAW;gBACX,IAAI,EAAE,gBAAgB;gBACtB,IAAI;gBACJ,MAAM;gBACN,WAAW;gBACX,eAAe;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,aAAa;gBAC3B,eAAe;aAChB,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,UAAU,EAAE,IAAI;YAChB,kBAAkB,EAAE,IAAI;YACxB,MAAM,EAAE,aAAa;YACrB,cAAc,EAAE,IAAI;SACrB,CAAC;IACJ,CAAC;IAED,iBAAiB;IACjB,OAAO;QACL,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,6BAA6B,CAAC,OAUtC;IACC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAEvD,MAAM,WAAW,GAAG,iBAAiB,CAAC;YACpC,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,eAAe,EAAE,OAAO,CAAC,eAAe;SACzC,CAAC,CAAC;QAEH,2BAA2B;QAC3B,WAAW,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACnD,WAAW,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,6BAA6B;QAE/D,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACzC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mDAAmD;QACnD,OAAO,CAAC,IAAI,CACV,mDAAmD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC5G,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAOE,EACF,WAAmB,EACnB,eAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkC,CAAC;IAE1D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,gBAAgB,CAAC;YAC9B,WAAW;YACX,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,MAAM,EAAE,SAAS,CAAC,MAAM;YACxB,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,eAAe,EAAE,SAAS,CAAC,eAAe;YAC1C,eAAe;YACf,cAAc,EAAE,IAAI;SACrB,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IAYrD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG;QACf,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,gBAAgB,CAAC,CAAC,MAAM;QAC5F,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,aAAa,CAAC,CAAC,MAAM;QACtF,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,MAAM;QAChF,GAAG,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,MAAM;KACxE,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAChC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,iBAAiB,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM;QAC/C,kBAAkB,EAAE,MAAM,CAAC,MAAM;QACjC,gBAAgB,EAAE,IAAI,CAAC,MAAM;QAC7B,QAAQ;QACR,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Inline Comment Parser
3
+ *
4
+ * Parses @behavioral-contract-ignore comments from TypeScript source files.
5
+ */
6
+ import * as ts from 'typescript';
7
+ import { InlineSuppressionComment } from './types.js';
8
+ /**
9
+ * Parse all inline suppression comments from a TypeScript source file
10
+ *
11
+ * @param sourceFile - TypeScript source file
12
+ * @returns Array of parsed suppression comments
13
+ */
14
+ export declare function parseInlineSuppressions(sourceFile: ts.SourceFile): InlineSuppressionComment[];
15
+ /**
16
+ * Check if a specific line has a suppression comment
17
+ *
18
+ * @param sourceFile - TypeScript source file
19
+ * @param targetLine - Line number to check (1-indexed)
20
+ * @returns Suppression comment if found, undefined otherwise
21
+ */
22
+ export declare function getSuppressionForLine(sourceFile: ts.SourceFile, targetLine: number): InlineSuppressionComment | undefined;
23
+ /**
24
+ * Check if a suppression comment applies to a specific violation
25
+ *
26
+ * @param suppression - Parsed suppression comment
27
+ * @param packageName - Package name from violation
28
+ * @param postconditionId - Postcondition ID from violation
29
+ * @returns True if suppression applies
30
+ */
31
+ export declare function suppressionMatches(suppression: InlineSuppressionComment, packageName: string, postconditionId: string): boolean;
32
+ /**
33
+ * Validate suppression comment format
34
+ *
35
+ * @param comment - Comment text to validate
36
+ * @returns Validation result
37
+ */
38
+ export declare function validateSuppressionComment(comment: string): {
39
+ valid: boolean;
40
+ error?: string;
41
+ };
42
+ /**
43
+ * Generate a suppression comment string
44
+ *
45
+ * @param packageName - Package name
46
+ * @param postconditionId - Postcondition ID
47
+ * @param reason - Human-readable reason
48
+ * @returns Formatted comment string
49
+ */
50
+ export declare function generateSuppressionComment(packageName: string, postconditionId: string, reason: string): string;
51
+ /**
52
+ * Extract all comments from a TypeScript node
53
+ *
54
+ * @param node - TypeScript AST node
55
+ * @param sourceFile - Source file containing the node
56
+ * @returns Array of comment texts
57
+ */
58
+ export declare function getNodeComments(node: ts.Node, sourceFile: ts.SourceFile): string[];
59
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/suppressions/parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AActD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,GACxB,wBAAwB,EAAE,CA+B5B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,UAAU,EAAE,MAAM,GACjB,wBAAwB,GAAG,SAAS,CAKtC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,wBAAwB,EACrC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,GACtB,OAAO,CAYT;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAqCA;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,UAAU,EAAE,EAAE,CAAC,UAAU,GACxB,MAAM,EAAE,CA+BV"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Inline Comment Parser
3
+ *
4
+ * Parses @behavioral-contract-ignore comments from TypeScript source files.
5
+ */
6
+ import * as ts from 'typescript';
7
+ /**
8
+ * Regular expression for matching suppression comments
9
+ *
10
+ * Format: @behavioral-contract-ignore <package>/<postcondition-id>: <reason>
11
+ *
12
+ * Examples:
13
+ * @behavioral-contract-ignore axios/network-failure: Global error handler
14
+ * @behavioral-contract-ignore STAR/timeout-not-set: Timeout set globally (use * for STAR)
15
+ * @behavioral-contract-ignore prisma/STAR: Framework handles all errors (use * for STAR)
16
+ */
17
+ const SUPPRESSION_COMMENT_REGEX = /@behavioral-contract-ignore\s+([\w@/-]+|\*)\/([\w-]+|\*):\s*(.+)/i;
18
+ /**
19
+ * Parse all inline suppression comments from a TypeScript source file
20
+ *
21
+ * @param sourceFile - TypeScript source file
22
+ * @returns Array of parsed suppression comments
23
+ */
24
+ export function parseInlineSuppressions(sourceFile) {
25
+ const suppressions = [];
26
+ const sourceText = sourceFile.getFullText();
27
+ const lines = sourceText.split('\n');
28
+ lines.forEach((line, index) => {
29
+ const trimmed = line.trim();
30
+ // Check if line is a comment
31
+ if (!trimmed.startsWith('//')) {
32
+ return;
33
+ }
34
+ // Try to match suppression format
35
+ const match = trimmed.match(SUPPRESSION_COMMENT_REGEX);
36
+ if (!match) {
37
+ return;
38
+ }
39
+ const [, packagePattern, postconditionPattern, reason] = match;
40
+ suppressions.push({
41
+ line: index + 1, // Convert to 1-indexed
42
+ package: packagePattern.trim(),
43
+ postconditionId: postconditionPattern.trim(),
44
+ reason: reason.trim(),
45
+ originalComment: trimmed
46
+ });
47
+ });
48
+ return suppressions;
49
+ }
50
+ /**
51
+ * Check if a specific line has a suppression comment
52
+ *
53
+ * @param sourceFile - TypeScript source file
54
+ * @param targetLine - Line number to check (1-indexed)
55
+ * @returns Suppression comment if found, undefined otherwise
56
+ */
57
+ export function getSuppressionForLine(sourceFile, targetLine) {
58
+ const suppressions = parseInlineSuppressions(sourceFile);
59
+ // Check the line before target (comment usually precedes code)
60
+ return suppressions.find(s => s.line === targetLine - 1 || s.line === targetLine);
61
+ }
62
+ /**
63
+ * Check if a suppression comment applies to a specific violation
64
+ *
65
+ * @param suppression - Parsed suppression comment
66
+ * @param packageName - Package name from violation
67
+ * @param postconditionId - Postcondition ID from violation
68
+ * @returns True if suppression applies
69
+ */
70
+ export function suppressionMatches(suppression, packageName, postconditionId) {
71
+ // Check package match (supports wildcards)
72
+ const packageMatches = suppression.package === '*' ||
73
+ suppression.package === packageName;
74
+ // Check postcondition match (supports wildcards)
75
+ const postconditionMatches = suppression.postconditionId === '*' ||
76
+ suppression.postconditionId === postconditionId;
77
+ return packageMatches && postconditionMatches;
78
+ }
79
+ /**
80
+ * Validate suppression comment format
81
+ *
82
+ * @param comment - Comment text to validate
83
+ * @returns Validation result
84
+ */
85
+ export function validateSuppressionComment(comment) {
86
+ const match = comment.match(SUPPRESSION_COMMENT_REGEX);
87
+ if (!match) {
88
+ return {
89
+ valid: false,
90
+ error: 'Invalid format. Expected: @behavioral-contract-ignore <package>/<postcondition-id>: <reason>'
91
+ };
92
+ }
93
+ const [, packagePattern, postconditionPattern, reason] = match;
94
+ // Validate reason is not empty
95
+ if (!reason || reason.trim().length === 0) {
96
+ return {
97
+ valid: false,
98
+ error: 'Reason is required. Provide explanation after colon (:)'
99
+ };
100
+ }
101
+ // Validate reason is meaningful (at least 10 characters)
102
+ if (reason.trim().length < 10) {
103
+ return {
104
+ valid: false,
105
+ error: 'Reason must be at least 10 characters. Provide meaningful explanation.'
106
+ };
107
+ }
108
+ // Warn about overly broad wildcards
109
+ if (packagePattern === '*' && postconditionPattern === '*') {
110
+ return {
111
+ valid: true, // Still valid, but warn
112
+ error: 'Warning: */* suppresses ALL violations. Use specific patterns when possible.'
113
+ };
114
+ }
115
+ return { valid: true };
116
+ }
117
+ /**
118
+ * Generate a suppression comment string
119
+ *
120
+ * @param packageName - Package name
121
+ * @param postconditionId - Postcondition ID
122
+ * @param reason - Human-readable reason
123
+ * @returns Formatted comment string
124
+ */
125
+ export function generateSuppressionComment(packageName, postconditionId, reason) {
126
+ return `// @behavioral-contract-ignore ${packageName}/${postconditionId}: ${reason}`;
127
+ }
128
+ /**
129
+ * Extract all comments from a TypeScript node
130
+ *
131
+ * @param node - TypeScript AST node
132
+ * @param sourceFile - Source file containing the node
133
+ * @returns Array of comment texts
134
+ */
135
+ export function getNodeComments(node, sourceFile) {
136
+ const comments = [];
137
+ const sourceText = sourceFile.getFullText();
138
+ // Get leading comments
139
+ const leadingComments = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
140
+ if (leadingComments) {
141
+ leadingComments.forEach(comment => {
142
+ const text = sourceText.substring(comment.pos, comment.end);
143
+ comments.push(text);
144
+ });
145
+ }
146
+ // Get trailing comments
147
+ const trailingComments = ts.getTrailingCommentRanges(sourceText, node.getEnd());
148
+ if (trailingComments) {
149
+ trailingComments.forEach(comment => {
150
+ const text = sourceText.substring(comment.pos, comment.end);
151
+ comments.push(text);
152
+ });
153
+ }
154
+ return comments;
155
+ }
156
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/suppressions/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Suppression System Types
3
+ *
4
+ * Defines data structures for suppressing false positive violations
5
+ * and tracking dead suppressions when the analyzer improves.
6
+ */
7
+ /**
8
+ * A single suppression entry
9
+ */
10
+ export interface Suppression {
11
+ /** Unique identifier: "suppress-{file}-{line}-{hash}" */
12
+ id: string;
13
+ /** Relative path from project root */
14
+ file: string;
15
+ /** Line number (1-indexed) */
16
+ line: number;
17
+ /** Optional column number (1-indexed) */
18
+ column?: number;
19
+ /** Package name (e.g., "axios") */
20
+ package: string;
21
+ /** Postcondition ID (e.g., "network-failure") */
22
+ postconditionId: string;
23
+ /** Human-readable reason for suppression */
24
+ reason: string;
25
+ /** ISO 8601 timestamp when suppression was created */
26
+ suppressedAt: string;
27
+ /** How the suppression was created */
28
+ suppressedBy: 'inline-comment' | 'config-file' | 'ai-agent' | 'cli';
29
+ /** ISO 8601 timestamp when last checked */
30
+ lastChecked: string;
31
+ /** Does this location still violate the contract? */
32
+ stillViolates: boolean;
33
+ /** Analyzer version that created this suppression */
34
+ analyzerVersion: string;
35
+ }
36
+ /**
37
+ * Manifest file tracking all suppressions
38
+ */
39
+ export interface SuppressionManifest {
40
+ /** Schema version */
41
+ version: string;
42
+ /** Absolute path to project root */
43
+ projectRoot: string;
44
+ /** ISO 8601 timestamp of last update */
45
+ lastUpdated: string;
46
+ /** All suppressions */
47
+ suppressions: Suppression[];
48
+ }
49
+ /**
50
+ * Configuration file structure (.behavioralcontractsrc.json)
51
+ */
52
+ export interface BehavioralContractsConfig {
53
+ /** Suppression rules */
54
+ ignore?: IgnoreRule[];
55
+ /** Other config options */
56
+ [key: string]: any;
57
+ }
58
+ /**
59
+ * A single ignore rule from config file
60
+ */
61
+ export interface IgnoreRule {
62
+ /** Glob pattern for files (e.g., "src/test/**") */
63
+ file?: string;
64
+ /** Package name to ignore */
65
+ package?: string;
66
+ /** Specific postcondition ID */
67
+ postconditionId?: string;
68
+ /** Required reason for suppression */
69
+ reason: string;
70
+ }
71
+ /**
72
+ * Result of parsing an inline suppression comment
73
+ */
74
+ export interface InlineSuppressionComment {
75
+ /** Line number where comment appears */
76
+ line: number;
77
+ /** Package name (or "*" for wildcard) */
78
+ package: string;
79
+ /** Postcondition ID (or "*" for wildcard) */
80
+ postconditionId: string;
81
+ /** Human-readable reason */
82
+ reason: string;
83
+ /** Full comment text */
84
+ originalComment: string;
85
+ }
86
+ /**
87
+ * A dead suppression that can be removed
88
+ */
89
+ export interface DeadSuppression {
90
+ /** The suppression that is no longer needed */
91
+ suppression: Suppression;
92
+ /** Analyzer version where it was fixed */
93
+ improvedInVersion: string;
94
+ /** Original analyzer version that needed suppression */
95
+ originalVersion: string;
96
+ /** Why the suppression is no longer needed */
97
+ improvementReason?: string;
98
+ }
99
+ /**
100
+ * Options for suppression matching
101
+ */
102
+ export interface SuppressionMatchOptions {
103
+ /** Enable wildcard matching */
104
+ allowWildcards?: boolean;
105
+ /** Case-insensitive matching */
106
+ caseInsensitive?: boolean;
107
+ }
108
+ /**
109
+ * Result of checking if a violation is suppressed
110
+ */
111
+ export interface SuppressionCheckResult {
112
+ /** Is this violation suppressed? */
113
+ suppressed: boolean;
114
+ /** The suppression that matched (if any) */
115
+ matchedSuppression?: Suppression | IgnoreRule;
116
+ /** How it was suppressed */
117
+ source?: 'inline-comment' | 'config-file';
118
+ /** Original comment or rule */
119
+ originalSource?: any;
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/suppressions/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IAEX,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IAEb,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IAEb,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAEhB,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;IAExB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IAEf,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IAErB,sCAAsC;IACtC,YAAY,EAAE,gBAAgB,GAAG,aAAa,GAAG,UAAU,GAAG,KAAK,CAAC;IAEpE,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IAEpB,qDAAqD;IACrD,aAAa,EAAE,OAAO,CAAC;IAEvB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAEhB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IAEpB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IAEpB,uBAAuB;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,wBAAwB;IACxB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IAEtB,2BAA2B;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IAEb,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAEhB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAC;IAExB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IAEf,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,WAAW,EAAE,WAAW,CAAC;IAEzB,0CAA0C;IAC1C,iBAAiB,EAAE,MAAM,CAAC;IAE1B,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IAExB,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,+BAA+B;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,gCAAgC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,oCAAoC;IACpC,UAAU,EAAE,OAAO,CAAC;IAEpB,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAE9C,4BAA4B;IAC5B,MAAM,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAC;IAE1C,+BAA+B;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAC;CACtB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Suppression System Types
3
+ *
4
+ * Defines data structures for suppressing false positive violations
5
+ * and tracking dead suppressions when the analyzer improves.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/suppressions/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behavioral-contracts/verify-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI tool for verifying TypeScript code against behavioral contracts",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,7 +22,9 @@
22
22
  "test:fixtures:watch": "vitest fixture-regression",
23
23
  "test:fixtures:generate": "node scripts/generate-expected-outputs.js",
24
24
  "lint": "eslint src --ext .ts",
25
- "clean": "rm -rf dist"
25
+ "clean": "rm -rf dist",
26
+ "verify": "node dist/index.js --tsconfig ./tsconfig.json --corpus ../corpus",
27
+ "verify:self": "npm run build && npm run verify"
26
28
  },
27
29
  "keywords": [
28
30
  "typescript",