@codeledger/cli 0.6.7 → 0.6.9

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 (175) hide show
  1. package/dist/architecture-graph/dependency-scanner.d.ts +16 -0
  2. package/dist/architecture-graph/dependency-scanner.d.ts.map +1 -0
  3. package/dist/architecture-graph/dependency-scanner.js +96 -0
  4. package/dist/architecture-graph/dependency-scanner.js.map +1 -0
  5. package/dist/architecture-graph/graph-builder.d.ts +70 -0
  6. package/dist/architecture-graph/graph-builder.d.ts.map +1 -0
  7. package/dist/architecture-graph/graph-builder.js +231 -0
  8. package/dist/architecture-graph/graph-builder.js.map +1 -0
  9. package/dist/architecture-graph/index.d.ts +4 -0
  10. package/dist/architecture-graph/index.d.ts.map +1 -0
  11. package/dist/architecture-graph/index.js +7 -0
  12. package/dist/architecture-graph/index.js.map +1 -0
  13. package/dist/architecture-graph/service-scanner.d.ts +22 -0
  14. package/dist/architecture-graph/service-scanner.d.ts.map +1 -0
  15. package/dist/architecture-graph/service-scanner.js +181 -0
  16. package/dist/architecture-graph/service-scanner.js.map +1 -0
  17. package/dist/commands/activate.d.ts.map +1 -1
  18. package/dist/commands/activate.js +16 -1
  19. package/dist/commands/activate.js.map +1 -1
  20. package/dist/commands/audit-export.d.ts +8 -0
  21. package/dist/commands/audit-export.d.ts.map +1 -0
  22. package/dist/commands/audit-export.js +190 -0
  23. package/dist/commands/audit-export.js.map +1 -0
  24. package/dist/commands/commit-msg.d.ts +58 -0
  25. package/dist/commands/commit-msg.d.ts.map +1 -0
  26. package/dist/commands/commit-msg.js +461 -0
  27. package/dist/commands/commit-msg.js.map +1 -0
  28. package/dist/commands/fix.d.ts +7 -0
  29. package/dist/commands/fix.d.ts.map +1 -0
  30. package/dist/commands/fix.js +107 -0
  31. package/dist/commands/fix.js.map +1 -0
  32. package/dist/commands/graph.d.ts +8 -0
  33. package/dist/commands/graph.d.ts.map +1 -0
  34. package/dist/commands/graph.js +29 -0
  35. package/dist/commands/graph.js.map +1 -0
  36. package/dist/commands/init.d.ts.map +1 -1
  37. package/dist/commands/init.js +14 -0
  38. package/dist/commands/init.js.map +1 -1
  39. package/dist/commands/intent.d.ts +3 -1
  40. package/dist/commands/intent.d.ts.map +1 -1
  41. package/dist/commands/intent.js +68 -2
  42. package/dist/commands/intent.js.map +1 -1
  43. package/dist/commands/learn.d.ts +8 -0
  44. package/dist/commands/learn.d.ts.map +1 -0
  45. package/dist/commands/learn.js +33 -0
  46. package/dist/commands/learn.js.map +1 -0
  47. package/dist/commands/pack.d.ts +12 -0
  48. package/dist/commands/pack.d.ts.map +1 -0
  49. package/dist/commands/pack.js +75 -0
  50. package/dist/commands/pack.js.map +1 -0
  51. package/dist/commands/pr-summary.d.ts +59 -0
  52. package/dist/commands/pr-summary.d.ts.map +1 -0
  53. package/dist/commands/pr-summary.js +524 -0
  54. package/dist/commands/pr-summary.js.map +1 -0
  55. package/dist/commands/serve.d.ts +13 -0
  56. package/dist/commands/serve.d.ts.map +1 -0
  57. package/dist/commands/serve.js +179 -0
  58. package/dist/commands/serve.js.map +1 -0
  59. package/dist/commands/session-cleanup.js +1 -0
  60. package/dist/commands/session-cleanup.js.map +1 -1
  61. package/dist/commands/session-summary.d.ts.map +1 -1
  62. package/dist/commands/session-summary.js +135 -3
  63. package/dist/commands/session-summary.js.map +1 -1
  64. package/dist/commands/setup-ci.d.ts +6 -4
  65. package/dist/commands/setup-ci.d.ts.map +1 -1
  66. package/dist/commands/setup-ci.js +228 -23
  67. package/dist/commands/setup-ci.js.map +1 -1
  68. package/dist/commands/verify.d.ts.map +1 -1
  69. package/dist/commands/verify.js +35 -3
  70. package/dist/commands/verify.js.map +1 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +127 -4
  73. package/dist/index.js.map +1 -1
  74. package/dist/intent/extractor.d.ts +15 -0
  75. package/dist/intent/extractor.d.ts.map +1 -0
  76. package/dist/intent/extractor.js +149 -0
  77. package/dist/intent/extractor.js.map +1 -0
  78. package/dist/intent/fetcher.d.ts +33 -0
  79. package/dist/intent/fetcher.d.ts.map +1 -0
  80. package/dist/intent/fetcher.js +364 -0
  81. package/dist/intent/fetcher.js.map +1 -0
  82. package/dist/intent/loader.d.ts +26 -0
  83. package/dist/intent/loader.d.ts.map +1 -0
  84. package/dist/intent/loader.js +80 -0
  85. package/dist/intent/loader.js.map +1 -0
  86. package/dist/intent/types.d.ts +33 -0
  87. package/dist/intent/types.d.ts.map +1 -0
  88. package/dist/intent/types.js +9 -0
  89. package/dist/intent/types.js.map +1 -0
  90. package/dist/project-config.d.ts +38 -0
  91. package/dist/project-config.d.ts.map +1 -0
  92. package/dist/project-config.js +206 -0
  93. package/dist/project-config.js.map +1 -0
  94. package/dist/review-intelligence/detectors/architecture-graph.d.ts +18 -0
  95. package/dist/review-intelligence/detectors/architecture-graph.d.ts.map +1 -0
  96. package/dist/review-intelligence/detectors/architecture-graph.js +73 -0
  97. package/dist/review-intelligence/detectors/architecture-graph.js.map +1 -0
  98. package/dist/review-intelligence/detectors/cjs-named-import.d.ts +13 -0
  99. package/dist/review-intelligence/detectors/cjs-named-import.d.ts.map +1 -0
  100. package/dist/review-intelligence/detectors/cjs-named-import.js +134 -0
  101. package/dist/review-intelligence/detectors/cjs-named-import.js.map +1 -0
  102. package/dist/review-intelligence/detectors/exact-count-assertion.d.ts +17 -0
  103. package/dist/review-intelligence/detectors/exact-count-assertion.d.ts.map +1 -0
  104. package/dist/review-intelligence/detectors/exact-count-assertion.js +93 -0
  105. package/dist/review-intelligence/detectors/exact-count-assertion.js.map +1 -0
  106. package/dist/review-intelligence/detectors/exports-map-missing.d.ts +14 -0
  107. package/dist/review-intelligence/detectors/exports-map-missing.d.ts.map +1 -0
  108. package/dist/review-intelligence/detectors/exports-map-missing.js +131 -0
  109. package/dist/review-intelligence/detectors/exports-map-missing.js.map +1 -0
  110. package/dist/review-intelligence/detectors/fixture-keyword-drift.d.ts +12 -0
  111. package/dist/review-intelligence/detectors/fixture-keyword-drift.d.ts.map +1 -0
  112. package/dist/review-intelligence/detectors/fixture-keyword-drift.js +82 -0
  113. package/dist/review-intelligence/detectors/fixture-keyword-drift.js.map +1 -0
  114. package/dist/review-intelligence/detectors/optional-infra-crash.d.ts +12 -0
  115. package/dist/review-intelligence/detectors/optional-infra-crash.d.ts.map +1 -0
  116. package/dist/review-intelligence/detectors/optional-infra-crash.js +97 -0
  117. package/dist/review-intelligence/detectors/optional-infra-crash.js.map +1 -0
  118. package/dist/review-intelligence/detectors/pack-rules.d.ts +16 -0
  119. package/dist/review-intelligence/detectors/pack-rules.d.ts.map +1 -0
  120. package/dist/review-intelligence/detectors/pack-rules.js +107 -0
  121. package/dist/review-intelligence/detectors/pack-rules.js.map +1 -0
  122. package/dist/review-intelligence/detectors/undeclared-dependency.d.ts +11 -0
  123. package/dist/review-intelligence/detectors/undeclared-dependency.d.ts.map +1 -0
  124. package/dist/review-intelligence/detectors/undeclared-dependency.js +100 -0
  125. package/dist/review-intelligence/detectors/undeclared-dependency.js.map +1 -0
  126. package/dist/review-intelligence/engine/ast-parser.d.ts +90 -0
  127. package/dist/review-intelligence/engine/ast-parser.d.ts.map +1 -0
  128. package/dist/review-intelligence/engine/ast-parser.js +266 -0
  129. package/dist/review-intelligence/engine/ast-parser.js.map +1 -0
  130. package/dist/review-intelligence/engine/dataflow.d.ts +34 -0
  131. package/dist/review-intelligence/engine/dataflow.d.ts.map +1 -0
  132. package/dist/review-intelligence/engine/dataflow.js +115 -0
  133. package/dist/review-intelligence/engine/dataflow.js.map +1 -0
  134. package/dist/review-intelligence/engine/index.d.ts +7 -0
  135. package/dist/review-intelligence/engine/index.d.ts.map +1 -0
  136. package/dist/review-intelligence/engine/index.js +7 -0
  137. package/dist/review-intelligence/engine/index.js.map +1 -0
  138. package/dist/review-intelligence/engine/symbol-resolver.d.ts +34 -0
  139. package/dist/review-intelligence/engine/symbol-resolver.d.ts.map +1 -0
  140. package/dist/review-intelligence/engine/symbol-resolver.js +106 -0
  141. package/dist/review-intelligence/engine/symbol-resolver.js.map +1 -0
  142. package/dist/review-intelligence/fixes/index.d.ts +54 -0
  143. package/dist/review-intelligence/fixes/index.d.ts.map +1 -0
  144. package/dist/review-intelligence/fixes/index.js +346 -0
  145. package/dist/review-intelligence/fixes/index.js.map +1 -0
  146. package/dist/review-intelligence/index.d.ts +4 -1
  147. package/dist/review-intelligence/index.d.ts.map +1 -1
  148. package/dist/review-intelligence/index.js +22 -3
  149. package/dist/review-intelligence/index.js.map +1 -1
  150. package/dist/review-intelligence/invariants.d.ts +15 -0
  151. package/dist/review-intelligence/invariants.d.ts.map +1 -1
  152. package/dist/review-intelligence/invariants.js +48 -4
  153. package/dist/review-intelligence/invariants.js.map +1 -1
  154. package/dist/review-intelligence/learning/index.d.ts +39 -0
  155. package/dist/review-intelligence/learning/index.d.ts.map +1 -0
  156. package/dist/review-intelligence/learning/index.js +265 -0
  157. package/dist/review-intelligence/learning/index.js.map +1 -0
  158. package/dist/review-intelligence/packs/index.d.ts +69 -0
  159. package/dist/review-intelligence/packs/index.d.ts.map +1 -0
  160. package/dist/review-intelligence/packs/index.js +168 -0
  161. package/dist/review-intelligence/packs/index.js.map +1 -0
  162. package/dist/review-intelligence/repair-guidance.d.ts.map +1 -1
  163. package/dist/review-intelligence/repair-guidance.js +46 -0
  164. package/dist/review-intelligence/repair-guidance.js.map +1 -1
  165. package/dist/review-intelligence/types.d.ts +18 -9
  166. package/dist/review-intelligence/types.d.ts.map +1 -1
  167. package/dist/review-intelligence/types.js +9 -1
  168. package/dist/review-intelligence/types.js.map +1 -1
  169. package/dist/templates/claude-md.d.ts.map +1 -1
  170. package/dist/templates/claude-md.js +23 -128
  171. package/dist/templates/claude-md.js.map +1 -1
  172. package/dist/templates/hooks.d.ts.map +1 -1
  173. package/dist/templates/hooks.js +39 -0
  174. package/dist/templates/hooks.js.map +1 -1
  175. package/package.json +9 -9
@@ -0,0 +1,134 @@
1
+ import { parseImports } from '../engine/ast-parser.js';
2
+ import { resolveModule } from '../engine/symbol-resolver.js';
3
+ /**
4
+ * Well-known CJS-only packages that commonly cause ESM interop issues.
5
+ * This list covers the most frequently encountered cases.
6
+ */
7
+ const KNOWN_CJS_PACKAGES = new Set([
8
+ 'express',
9
+ 'chalk',
10
+ 'ora',
11
+ 'inquirer',
12
+ 'commander',
13
+ 'yargs',
14
+ 'debug',
15
+ 'dotenv',
16
+ 'lodash',
17
+ 'moment',
18
+ 'winston',
19
+ 'morgan',
20
+ 'cors',
21
+ 'helmet',
22
+ 'compression',
23
+ 'cookie-parser',
24
+ 'body-parser',
25
+ 'multer',
26
+ 'passport',
27
+ 'jsonwebtoken',
28
+ 'bcrypt',
29
+ 'bcryptjs',
30
+ 'mongoose',
31
+ 'sequelize',
32
+ 'knex',
33
+ 'pg',
34
+ 'mysql2',
35
+ 'redis',
36
+ 'ioredis',
37
+ 'bull',
38
+ 'node-cron',
39
+ 'nodemailer',
40
+ 'handlebars',
41
+ 'ejs',
42
+ 'pug',
43
+ 'cheerio',
44
+ 'puppeteer',
45
+ 'sharp',
46
+ 'jimp',
47
+ 'uuid',
48
+ 'shortid',
49
+ 'nanoid',
50
+ 'glob',
51
+ 'minimatch',
52
+ 'minimist',
53
+ 'semver',
54
+ 'rimraf',
55
+ 'mkdirp',
56
+ 'cross-env',
57
+ ]);
58
+ /**
59
+ * INVARIANT — cjs_named_import
60
+ *
61
+ * Detect named imports from known CommonJS-only packages in ESM files.
62
+ * Named imports from CJS modules fail at runtime with:
63
+ * SyntaxError: Named export 'X' not found. The requested module 'Y'
64
+ * is a CommonJS module, which may not support all module.exports as named exports.
65
+ *
66
+ * Severity: P1 (runtime crash)
67
+ */
68
+ export const cjsNamedImportDetector = {
69
+ name: 'build_runtime',
70
+ analyze(ctx) {
71
+ const findings = [];
72
+ for (const filePath of ctx.files) {
73
+ const content = ctx.fileContents.get(filePath);
74
+ if (!content)
75
+ continue;
76
+ if (!isAnalyzableFile(filePath))
77
+ continue;
78
+ // Only check ESM files
79
+ if (!isEsmFile(filePath, content))
80
+ continue;
81
+ const imports = parseImports(content);
82
+ for (const imp of imports) {
83
+ if (imp.isTypeOnly)
84
+ continue;
85
+ if (imp.namedImports.length === 0)
86
+ continue;
87
+ if (imp.isDynamic)
88
+ continue;
89
+ const resolved = resolveModule(imp.source);
90
+ if (resolved.kind !== 'external')
91
+ continue;
92
+ if (!resolved.packageName)
93
+ continue;
94
+ if (KNOWN_CJS_PACKAGES.has(resolved.packageName)) {
95
+ const namedList = imp.namedImports.join(', ');
96
+ findings.push({
97
+ rule_id: 'RI-CJS-001',
98
+ title: 'Named import from CommonJS module',
99
+ severity: 'P1',
100
+ category: 'build_runtime',
101
+ file: filePath,
102
+ line: imp.line,
103
+ trigger: `import { ${namedList} } from '${imp.source}'`,
104
+ missing_companions: [
105
+ `Use default import: import ${resolved.packageName.replace(/[^a-zA-Z]/g, '')} from '${imp.source}'`,
106
+ ],
107
+ why_it_matters: `'${resolved.packageName}' is a CommonJS module. Named imports from CJS modules ` +
108
+ 'may fail at runtime in ESM contexts with "SyntaxError: Named export not found". ' +
109
+ 'Use a default import and destructure, or use createRequire().',
110
+ confidence: 'medium',
111
+ });
112
+ }
113
+ }
114
+ }
115
+ return findings;
116
+ },
117
+ };
118
+ /**
119
+ * Check if a file is likely an ESM file.
120
+ */
121
+ function isEsmFile(filePath, _content) {
122
+ // .mts, .mjs are always ESM
123
+ if (/\.m[jt]s$/.test(filePath))
124
+ return true;
125
+ // .ts and .js could be either — assume ESM in modern codebases
126
+ // (this is a heuristic; precise detection requires checking package.json type)
127
+ if (/\.(?:ts|tsx)$/.test(filePath))
128
+ return true;
129
+ return false;
130
+ }
131
+ function isAnalyzableFile(filePath) {
132
+ return /\.(?:ts|tsx|js|jsx|mts|mjs)$/.test(filePath);
133
+ }
134
+ //# sourceMappingURL=cjs-named-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cjs-named-import.js","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/cjs-named-import.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { InvariantDetector } from '../types.js';
2
+ /**
3
+ * INVARIANT — exact_count_assertion_risk
4
+ *
5
+ * Detect brittle exact-count assertions in test files, especially
6
+ * in search/filter/query tests where the exact count depends on
7
+ * fixture data that may change.
8
+ *
9
+ * Patterns detected:
10
+ * expect(results.length).toBe(N)
11
+ * expect(results).toHaveLength(N)
12
+ * assert.equal(results.length, N)
13
+ *
14
+ * Severity: P2 (brittle tests that break on data changes)
15
+ */
16
+ export declare const exactCountAssertionDetector: InvariantDetector;
17
+ //# sourceMappingURL=exact-count-assertion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-count-assertion.d.ts","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/exact-count-assertion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,EAAE,iBA2DzC,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * INVARIANT — exact_count_assertion_risk
3
+ *
4
+ * Detect brittle exact-count assertions in test files, especially
5
+ * in search/filter/query tests where the exact count depends on
6
+ * fixture data that may change.
7
+ *
8
+ * Patterns detected:
9
+ * expect(results.length).toBe(N)
10
+ * expect(results).toHaveLength(N)
11
+ * assert.equal(results.length, N)
12
+ *
13
+ * Severity: P2 (brittle tests that break on data changes)
14
+ */
15
+ export const exactCountAssertionDetector = {
16
+ name: 'test_integrity',
17
+ analyze(ctx) {
18
+ const findings = [];
19
+ for (const filePath of ctx.files) {
20
+ const content = ctx.fileContents.get(filePath);
21
+ if (!content)
22
+ continue;
23
+ // Only analyze test files
24
+ if (!isTestFile(filePath))
25
+ continue;
26
+ const lines = content.split('\n');
27
+ let inSearchContext = false;
28
+ for (let i = 0; i < lines.length; i++) {
29
+ const line = lines[i];
30
+ const trimmed = line.trim();
31
+ // Skip comments
32
+ if (trimmed.startsWith('//') || trimmed.startsWith('*'))
33
+ continue;
34
+ // Track search/filter/query context
35
+ if (/\b(?:search|filter|query|find|list|getAll)\s*\(/.test(trimmed)) {
36
+ inSearchContext = true;
37
+ }
38
+ // Reset context at test boundaries
39
+ if (/\b(?:it|test|describe)\s*\(/.test(trimmed)) {
40
+ inSearchContext = false;
41
+ }
42
+ // Detect exact count assertions
43
+ const countMatch = detectExactCountAssertion(trimmed);
44
+ if (countMatch && inSearchContext) {
45
+ findings.push({
46
+ rule_id: 'RI-EC-001',
47
+ title: 'Brittle exact count assertion in search/filter test',
48
+ severity: 'P2',
49
+ category: 'test_integrity',
50
+ file: filePath,
51
+ line: i + 1,
52
+ trigger: countMatch.pattern,
53
+ missing_companions: [
54
+ 'Use toBeGreaterThan(0), toBeGreaterThanOrEqual(N), or data-driven counts',
55
+ ],
56
+ why_it_matters: `Exact count assertion (${countMatch.count}) in a search/filter test is brittle. ` +
57
+ 'If fixture data changes, this assertion breaks even though the search logic is correct. ' +
58
+ 'Prefer range assertions or derive expected counts from the test data.',
59
+ confidence: 'medium',
60
+ });
61
+ }
62
+ }
63
+ }
64
+ return findings;
65
+ },
66
+ };
67
+ function detectExactCountAssertion(line) {
68
+ // expect(x.length).toBe(N)
69
+ const toBeLengthMatch = /expect\s*\([^)]*\.length\s*\)\s*\.toBe\s*\(\s*(\d+)\s*\)/.exec(line);
70
+ if (toBeLengthMatch) {
71
+ return { pattern: `expect(x.length).toBe(${toBeLengthMatch[1]})`, count: toBeLengthMatch[1] };
72
+ }
73
+ // expect(x).toHaveLength(N)
74
+ const haveLengthMatch = /expect\s*\([^)]*\)\s*\.toHaveLength\s*\(\s*(\d+)\s*\)/.exec(line);
75
+ if (haveLengthMatch) {
76
+ return { pattern: `expect(x).toHaveLength(${haveLengthMatch[1]})`, count: haveLengthMatch[1] };
77
+ }
78
+ // assert.equal(x.length, N) or assert.strictEqual(x.length, N)
79
+ const assertMatch = /assert\s*\.(?:equal|strictEqual)\s*\([^,]*\.length\s*,\s*(\d+)\s*\)/.exec(line);
80
+ if (assertMatch) {
81
+ return { pattern: `assert.equal(x.length, ${assertMatch[1]})`, count: assertMatch[1] };
82
+ }
83
+ // expect(x.length).toEqual(N)
84
+ const toEqualMatch = /expect\s*\([^)]*\.length\s*\)\s*\.toEqual\s*\(\s*(\d+)\s*\)/.exec(line);
85
+ if (toEqualMatch) {
86
+ return { pattern: `expect(x.length).toEqual(${toEqualMatch[1]})`, count: toEqualMatch[1] };
87
+ }
88
+ return undefined;
89
+ }
90
+ function isTestFile(filePath) {
91
+ return /\.(?:test|spec|e2e)\.(?:ts|tsx|js|jsx|mts|mjs)$/.test(filePath);
92
+ }
93
+ //# sourceMappingURL=exact-count-assertion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-count-assertion.js","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/exact-count-assertion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { InvariantDetector } from '../types.js';
2
+ /**
3
+ * INVARIANT — exports_map_missing
4
+ *
5
+ * Detect subpath imports from packages that have an exports map but
6
+ * don't include the imported subpath.
7
+ *
8
+ * Also detects workspace packages missing exports map entries for
9
+ * subpath imports used by other packages.
10
+ *
11
+ * Severity: P1 (causes Node.js ERR_PACKAGE_PATH_NOT_EXPORTED)
12
+ */
13
+ export declare const exportsMapMissingDetector: InvariantDetector;
14
+ //# sourceMappingURL=exports-map-missing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports-map-missing.d.ts","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/exports-map-missing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,aAAa,CAAC;AAIrB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,EAAE,iBAoDvC,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseImports } from '../engine/ast-parser.js';
4
+ import { resolveModule } from '../engine/symbol-resolver.js';
5
+ /**
6
+ * INVARIANT — exports_map_missing
7
+ *
8
+ * Detect subpath imports from packages that have an exports map but
9
+ * don't include the imported subpath.
10
+ *
11
+ * Also detects workspace packages missing exports map entries for
12
+ * subpath imports used by other packages.
13
+ *
14
+ * Severity: P1 (causes Node.js ERR_PACKAGE_PATH_NOT_EXPORTED)
15
+ */
16
+ export const exportsMapMissingDetector = {
17
+ name: 'build_runtime',
18
+ analyze(ctx) {
19
+ const findings = [];
20
+ const exportsCache = new Map();
21
+ for (const filePath of ctx.files) {
22
+ const content = ctx.fileContents.get(filePath);
23
+ if (!content)
24
+ continue;
25
+ if (!isAnalyzableFile(filePath))
26
+ continue;
27
+ const imports = parseImports(content);
28
+ for (const imp of imports) {
29
+ const resolved = resolveModule(imp.source);
30
+ // Only check external packages with subpaths
31
+ if (resolved.kind !== 'external')
32
+ continue;
33
+ if (!resolved.subpath)
34
+ continue;
35
+ if (!resolved.packageName)
36
+ continue;
37
+ // Check if the package has an exports map
38
+ const exportsMap = getPackageExportsMap(ctx.cwd, resolved.packageName, exportsCache);
39
+ if (exportsMap === null)
40
+ continue; // No exports map = no restriction
41
+ // Check if the subpath is covered by the exports map
42
+ const subpathEntry = '.' + resolved.subpath;
43
+ if (!isSubpathExported(subpathEntry, exportsMap)) {
44
+ findings.push({
45
+ rule_id: 'RI-EM-001',
46
+ title: 'Subpath not in package exports map',
47
+ severity: 'P1',
48
+ category: 'build_runtime',
49
+ file: filePath,
50
+ line: imp.line,
51
+ trigger: `import '${imp.source}' — subpath '${subpathEntry}' not exported`,
52
+ missing_companions: [
53
+ `Add '${subpathEntry}' to the exports map of '${resolved.packageName}/package.json'`,
54
+ ],
55
+ why_it_matters: `The subpath '${subpathEntry}' is not listed in the exports map of '${resolved.packageName}'. ` +
56
+ 'Node.js will throw ERR_PACKAGE_PATH_NOT_EXPORTED at runtime. ' +
57
+ 'Either add the subpath to the package exports or use a different import path.',
58
+ confidence: 'high',
59
+ });
60
+ }
61
+ }
62
+ }
63
+ return findings;
64
+ },
65
+ };
66
+ /**
67
+ * Load the exports map from a package's package.json.
68
+ * Returns null if no exports map exists (package allows any subpath).
69
+ */
70
+ function getPackageExportsMap(cwd, packageName, cache) {
71
+ const cached = cache.get(packageName);
72
+ if (cached !== undefined)
73
+ return cached;
74
+ // Look in node_modules
75
+ const pkgJsonPath = join(cwd, 'node_modules', packageName, 'package.json');
76
+ if (!existsSync(pkgJsonPath)) {
77
+ // Also check workspace packages
78
+ const workspacePath = join(cwd, 'packages', packageName.replace(/^@\w+\//, ''), 'package.json');
79
+ if (existsSync(workspacePath)) {
80
+ return loadExportsFromPath(workspacePath, packageName, cache);
81
+ }
82
+ cache.set(packageName, null);
83
+ return null;
84
+ }
85
+ return loadExportsFromPath(pkgJsonPath, packageName, cache);
86
+ }
87
+ function loadExportsFromPath(pkgJsonPath, packageName, cache) {
88
+ try {
89
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
90
+ const exportsField = pkg['exports'];
91
+ if (!exportsField || typeof exportsField !== 'object') {
92
+ cache.set(packageName, null);
93
+ return null;
94
+ }
95
+ cache.set(packageName, exportsField);
96
+ return exportsField;
97
+ }
98
+ catch {
99
+ cache.set(packageName, null);
100
+ return null;
101
+ }
102
+ }
103
+ /**
104
+ * Check if a subpath is covered by an exports map.
105
+ * Handles both exact matches and wildcard patterns.
106
+ */
107
+ function isSubpathExported(subpath, exportsMap) {
108
+ // Direct match
109
+ if (subpath in exportsMap)
110
+ return true;
111
+ // Wildcard pattern matching (e.g. "./*" or "./lib/*")
112
+ for (const key of Object.keys(exportsMap)) {
113
+ if (key.includes('*')) {
114
+ const pattern = key.replace('*', '(.+)');
115
+ if (new RegExp(`^${pattern}$`).test(subpath)) {
116
+ return true;
117
+ }
118
+ }
119
+ }
120
+ // Nested condition maps (e.g. exports: { ".": { import: "...", require: "..." } })
121
+ // If the top-level key doesn't start with '.', it's a condition map for "."
122
+ const hasConditionKeys = Object.keys(exportsMap).some((k) => !k.startsWith('.') && k !== 'default');
123
+ if (hasConditionKeys && subpath === '.') {
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ function isAnalyzableFile(filePath) {
129
+ return /\.(?:ts|tsx|js|jsx|mts|mjs)$/.test(filePath);
130
+ }
131
+ //# sourceMappingURL=exports-map-missing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports-map-missing.js","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/exports-map-missing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { InvariantDetector } from '../types.js';
2
+ /**
3
+ * INVARIANT — fixture_keyword_drift
4
+ *
5
+ * Detect test expectations that rely on specific string/keyword values
6
+ * that may have drifted from the actual fixture data. Common in search
7
+ * tests where the expected keyword must exist in the test fixtures.
8
+ *
9
+ * Severity: P2 (can cause flaky tests)
10
+ */
11
+ export declare const fixtureKeywordDriftDetector: InvariantDetector;
12
+ //# sourceMappingURL=fixture-keyword-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-keyword-drift.d.ts","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/fixture-keyword-drift.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,EAAE,iBAgDzC,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * INVARIANT — fixture_keyword_drift
3
+ *
4
+ * Detect test expectations that rely on specific string/keyword values
5
+ * that may have drifted from the actual fixture data. Common in search
6
+ * tests where the expected keyword must exist in the test fixtures.
7
+ *
8
+ * Severity: P2 (can cause flaky tests)
9
+ */
10
+ export const fixtureKeywordDriftDetector = {
11
+ name: 'test_integrity',
12
+ analyze(ctx) {
13
+ const findings = [];
14
+ for (const filePath of ctx.files) {
15
+ const content = ctx.fileContents.get(filePath);
16
+ if (!content)
17
+ continue;
18
+ // Only analyze test files
19
+ if (!isTestFile(filePath))
20
+ continue;
21
+ const lines = content.split('\n');
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i];
24
+ const trimmed = line.trim();
25
+ // Skip comments
26
+ if (trimmed.startsWith('//') || trimmed.startsWith('*'))
27
+ continue;
28
+ // Detect hardcoded search/filter expectations
29
+ // Pattern: searching for a specific keyword and then asserting results contain it
30
+ const searchAssertMatch = detectSearchKeywordAssertion(trimmed, lines, i);
31
+ if (searchAssertMatch) {
32
+ findings.push({
33
+ rule_id: 'RI-FK-001',
34
+ title: 'Test relies on hardcoded fixture keyword',
35
+ severity: 'P2',
36
+ category: 'test_integrity',
37
+ file: filePath,
38
+ line: i + 1,
39
+ trigger: searchAssertMatch,
40
+ missing_companions: [
41
+ 'Extract keyword from fixture data or use a data-driven approach',
42
+ ],
43
+ why_it_matters: 'Hardcoded keywords in test assertions can silently become wrong if fixture data changes. ' +
44
+ 'Extract the expected keyword from the fixture or seed data to keep the test robust.',
45
+ confidence: 'medium',
46
+ });
47
+ }
48
+ }
49
+ }
50
+ return findings;
51
+ },
52
+ };
53
+ /**
54
+ * Detect patterns where a test searches for a hardcoded string and then
55
+ * asserts on the results.
56
+ */
57
+ function detectSearchKeywordAssertion(line, allLines, lineIndex) {
58
+ // Pattern 1: search/filter with a hardcoded string literal, followed by assertion
59
+ const searchPatterns = [
60
+ /(?:search|filter|find|query)\s*\(\s*['"]([^'"]{3,})['"]\s*\)/,
61
+ /(?:search|filter|find|query)\s*\(\s*\{[^}]*(?:keyword|query|term|q)\s*:\s*['"]([^'"]{3,})['"]/,
62
+ /(?:where|contains|includes|match)\s*\(\s*['"]([^'"]{3,})['"]\s*\)/,
63
+ ];
64
+ for (const pattern of searchPatterns) {
65
+ const match = pattern.exec(line);
66
+ if (match) {
67
+ // Check if there's an assertion within the next 10 lines
68
+ const windowEnd = Math.min(allLines.length, lineIndex + 10);
69
+ for (let j = lineIndex + 1; j < windowEnd; j++) {
70
+ const nextLine = allLines[j];
71
+ if (nextLine && /expect\s*\(/.test(nextLine)) {
72
+ return `hardcoded search keyword '${match[1]}'`;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return undefined;
78
+ }
79
+ function isTestFile(filePath) {
80
+ return /\.(?:test|spec|e2e)\.(?:ts|tsx|js|jsx|mts|mjs)$/.test(filePath);
81
+ }
82
+ //# sourceMappingURL=fixture-keyword-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-keyword-drift.js","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/fixture-keyword-drift.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC5D,IAAI,EAAE,gBAAgB;IAEtB,OAAO,CAAC,GAAoB;QAC1B,MAAM,QAAQ,GAAoB,EAAE,CAAC;QAErC,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,CAAC,OAAO;gBAAE,SAAS;YAEvB,0BAA0B;YAC1B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;gBACvB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAE5B,gBAAgB;gBAChB,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAElE,8CAA8C;gBAC9C,kFAAkF;gBAClF,MAAM,iBAAiB,GAAG,4BAA4B,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC1E,IAAI,iBAAiB,EAAE,CAAC;oBACtB,QAAQ,CAAC,IAAI,CAAC;wBACZ,OAAO,EAAE,WAAW;wBACpB,KAAK,EAAE,0CAA0C;wBACjD,QAAQ,EAAE,IAAI;wBACd,QAAQ,EAAE,gBAAgB;wBAC1B,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,OAAO,EAAE,iBAAiB;wBAC1B,kBAAkB,EAAE;4BAClB,iEAAiE;yBAClE;wBACD,cAAc,EACZ,2FAA2F;4BAC3F,qFAAqF;wBACvF,UAAU,EAAE,QAAQ;qBACrB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF;;;GAGG;AACH,SAAS,4BAA4B,CACnC,IAAY,EACZ,QAAkB,EAClB,SAAiB;IAEjB,kFAAkF;IAClF,MAAM,cAAc,GAAG;QACrB,8DAA8D;QAC9D,+FAA+F;QAC/F,mEAAmE;KACpE,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,KAAK,EAAE,CAAC;YACV,yDAAyD;YACzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,EAAE,CAAC,CAAC;YAC5D,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAC7B,IAAI,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7C,OAAO,6BAA6B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;gBAClD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,UAAU,CAAC,QAAgB;IAClC,OAAO,iDAAiD,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { InvariantDetector } from '../types.js';
2
+ /**
3
+ * INVARIANT — optional_infra_crash
4
+ *
5
+ * Detect telemetry, metrics, tracing, or other optional infrastructure
6
+ * initialization that is NOT wrapped in try/catch. If these optional
7
+ * services are down, the application should not crash on startup.
8
+ *
9
+ * Severity: P1 (can crash production on infra issues)
10
+ */
11
+ export declare const optionalInfraCrashDetector: InvariantDetector;
12
+ //# sourceMappingURL=optional-infra-crash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optional-infra-crash.d.ts","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/optional-infra-crash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,EAAE,iBAgDxC,CAAC"}
@@ -0,0 +1,97 @@
1
+ import { isInsideTryCatch } from '../engine/dataflow.js';
2
+ /**
3
+ * INVARIANT — optional_infra_crash
4
+ *
5
+ * Detect telemetry, metrics, tracing, or other optional infrastructure
6
+ * initialization that is NOT wrapped in try/catch. If these optional
7
+ * services are down, the application should not crash on startup.
8
+ *
9
+ * Severity: P1 (can crash production on infra issues)
10
+ */
11
+ export const optionalInfraCrashDetector = {
12
+ name: 'build_runtime',
13
+ analyze(ctx) {
14
+ const findings = [];
15
+ for (const filePath of ctx.files) {
16
+ const content = ctx.fileContents.get(filePath);
17
+ if (!content)
18
+ continue;
19
+ if (!isAnalyzableFile(filePath))
20
+ continue;
21
+ const lines = content.split('\n');
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i];
24
+ const trimmed = line.trim();
25
+ // Skip comments
26
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
27
+ continue;
28
+ if (trimmed.startsWith('import '))
29
+ continue;
30
+ // Detect optional infra initialization patterns
31
+ const infraMatch = matchesInfraInit(trimmed);
32
+ if (!infraMatch)
33
+ continue;
34
+ // Check if this line is inside a try/catch block
35
+ const tryCatchInfo = isInsideTryCatch(content, i + 1);
36
+ if (tryCatchInfo.isWrapped)
37
+ continue;
38
+ findings.push({
39
+ rule_id: 'RI-IC-001',
40
+ title: 'Optional infrastructure init without try/catch',
41
+ severity: 'P1',
42
+ category: 'build_runtime',
43
+ file: filePath,
44
+ line: i + 1,
45
+ trigger: infraMatch,
46
+ missing_companions: ['Wrap in try/catch to prevent crash when infra is unavailable'],
47
+ why_it_matters: 'Optional infrastructure (telemetry, metrics, tracing) should never crash the application ' +
48
+ 'if the backing service is unavailable. Wrap initialization in try/catch and degrade gracefully.',
49
+ confidence: 'medium',
50
+ });
51
+ }
52
+ }
53
+ return findings;
54
+ },
55
+ };
56
+ /**
57
+ * Check if a line matches an optional infra initialization pattern.
58
+ * Returns the matched description or undefined.
59
+ */
60
+ function matchesInfraInit(line) {
61
+ const patterns = [
62
+ {
63
+ pattern: /\b(?:initTelemetry|setupTelemetry|startTelemetry|initTracing|setupTracing|startTracing)\s*\(/,
64
+ description: 'telemetry/tracing initialization',
65
+ },
66
+ {
67
+ pattern: /\b(?:initMetrics|setupMetrics|startMetrics|metricsClient|statsClient)\s*[.(]/,
68
+ description: 'metrics initialization',
69
+ },
70
+ {
71
+ pattern: /\b(?:DatadogTracer|dd-trace|opentelemetry|@opentelemetry)\b/,
72
+ description: 'tracing SDK initialization',
73
+ },
74
+ {
75
+ pattern: /\b(?:StatsD|statsd|collectd|prometheus)\s*[.(]/i,
76
+ description: 'metrics SDK initialization',
77
+ },
78
+ {
79
+ pattern: /\b(?:Sentry\.init|initSentry|setupSentry|Bugsnag\.start)\s*\(/,
80
+ description: 'error tracking initialization',
81
+ },
82
+ {
83
+ pattern: /\b(?:newrelic|elasticApm|apm\.start)\b/,
84
+ description: 'APM initialization',
85
+ },
86
+ ];
87
+ for (const { pattern, description } of patterns) {
88
+ if (pattern.test(line)) {
89
+ return description;
90
+ }
91
+ }
92
+ return undefined;
93
+ }
94
+ function isAnalyzableFile(filePath) {
95
+ return /\.(?:ts|tsx|js|jsx|mts|mjs)$/.test(filePath);
96
+ }
97
+ //# sourceMappingURL=optional-infra-crash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optional-infra-crash.js","sourceRoot":"","sources":["../../../src/review-intelligence/detectors/optional-infra-crash.ts"],"names":[],"mappings":"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"}