tlc-claude-code 1.6.4 → 1.7.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 (81) hide show
  1. package/dashboard/dist/components/ContainerSecurityPane.d.ts +45 -0
  2. package/dashboard/dist/components/ContainerSecurityPane.js +44 -0
  3. package/dashboard/dist/components/ContainerSecurityPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/ContainerSecurityPane.test.js +153 -0
  5. package/package.json +1 -1
  6. package/server/lib/access-control.test.js +1 -1
  7. package/server/lib/agents-cancel-command.test.js +1 -1
  8. package/server/lib/agents-get-command.test.js +1 -1
  9. package/server/lib/agents-list-command.test.js +1 -1
  10. package/server/lib/agents-logs-command.test.js +1 -1
  11. package/server/lib/agents-retry-command.test.js +1 -1
  12. package/server/lib/budget-limits.test.js +2 -2
  13. package/server/lib/code-gate/bypass-logger.js +129 -0
  14. package/server/lib/code-gate/bypass-logger.test.js +142 -0
  15. package/server/lib/code-gate/gate-command.js +114 -0
  16. package/server/lib/code-gate/gate-command.test.js +111 -0
  17. package/server/lib/code-gate/gate-config.js +163 -0
  18. package/server/lib/code-gate/gate-config.test.js +181 -0
  19. package/server/lib/code-gate/gate-engine.js +193 -0
  20. package/server/lib/code-gate/gate-engine.test.js +258 -0
  21. package/server/lib/code-gate/gate-reporter.js +123 -0
  22. package/server/lib/code-gate/gate-reporter.test.js +159 -0
  23. package/server/lib/code-gate/hooks-generator.js +149 -0
  24. package/server/lib/code-gate/hooks-generator.test.js +142 -0
  25. package/server/lib/code-gate/llm-reviewer.js +176 -0
  26. package/server/lib/code-gate/llm-reviewer.test.js +161 -0
  27. package/server/lib/code-gate/push-gate.js +133 -0
  28. package/server/lib/code-gate/push-gate.test.js +190 -0
  29. package/server/lib/code-gate/rules/architecture-rules.js +228 -0
  30. package/server/lib/code-gate/rules/architecture-rules.test.js +155 -0
  31. package/server/lib/code-gate/rules/client-rules.js +120 -0
  32. package/server/lib/code-gate/rules/client-rules.test.js +121 -0
  33. package/server/lib/code-gate/rules/config-rules.js +140 -0
  34. package/server/lib/code-gate/rules/config-rules.test.js +103 -0
  35. package/server/lib/code-gate/rules/database-rules.js +158 -0
  36. package/server/lib/code-gate/rules/database-rules.test.js +119 -0
  37. package/server/lib/code-gate/rules/docker-rules.js +201 -0
  38. package/server/lib/code-gate/rules/docker-rules.test.js +104 -0
  39. package/server/lib/code-gate/rules/quality-rules.js +304 -0
  40. package/server/lib/code-gate/rules/quality-rules.test.js +199 -0
  41. package/server/lib/code-gate/rules/security-rules.js +228 -0
  42. package/server/lib/code-gate/rules/security-rules.test.js +131 -0
  43. package/server/lib/code-gate/rules/structure-rules.js +155 -0
  44. package/server/lib/code-gate/rules/structure-rules.test.js +107 -0
  45. package/server/lib/code-gate/rules/test-rules.js +93 -0
  46. package/server/lib/code-gate/rules/test-rules.test.js +97 -0
  47. package/server/lib/code-gate/typescript-gate.js +128 -0
  48. package/server/lib/code-gate/typescript-gate.test.js +131 -0
  49. package/server/lib/code-generator.test.js +1 -1
  50. package/server/lib/cost-command.test.js +1 -1
  51. package/server/lib/cost-optimizer.test.js +1 -1
  52. package/server/lib/cost-projections.test.js +1 -1
  53. package/server/lib/cost-reports.test.js +1 -1
  54. package/server/lib/cost-tracker.test.js +1 -1
  55. package/server/lib/crypto-patterns.test.js +1 -1
  56. package/server/lib/design-command.test.js +1 -1
  57. package/server/lib/design-parser.test.js +1 -1
  58. package/server/lib/gemini-vision.test.js +1 -1
  59. package/server/lib/input-validator.test.js +1 -1
  60. package/server/lib/litellm-client.test.js +1 -1
  61. package/server/lib/litellm-command.test.js +1 -1
  62. package/server/lib/litellm-config.test.js +1 -1
  63. package/server/lib/model-pricing.test.js +1 -1
  64. package/server/lib/models-command.test.js +1 -1
  65. package/server/lib/optimize-command.test.js +1 -1
  66. package/server/lib/orchestration-integration.test.js +1 -1
  67. package/server/lib/output-encoder.test.js +1 -1
  68. package/server/lib/quality-evaluator.test.js +1 -1
  69. package/server/lib/quality-gate-command.test.js +1 -1
  70. package/server/lib/quality-gate-scorer.test.js +1 -1
  71. package/server/lib/quality-history.test.js +1 -1
  72. package/server/lib/quality-presets.test.js +1 -1
  73. package/server/lib/quality-retry.test.js +1 -1
  74. package/server/lib/quality-thresholds.test.js +1 -1
  75. package/server/lib/secure-auth.test.js +1 -1
  76. package/server/lib/secure-code-command.test.js +1 -1
  77. package/server/lib/secure-errors.test.js +1 -1
  78. package/server/lib/security/auth-security.test.js +4 -3
  79. package/server/lib/vision-command.test.js +1 -1
  80. package/server/lib/visual-command.test.js +1 -1
  81. package/server/lib/visual-testing.test.js +1 -1
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Quality Rules
3
+ *
4
+ * Detects hardcoded values, oversized functions, console.log leftovers,
5
+ * TODO/FIXME without issue references, and other code quality issues.
6
+ *
7
+ * Each check function accepts (filePath, content) and returns an array
8
+ * of findings: { severity, rule, line, message, fix }
9
+ *
10
+ * @module code-gate/rules/quality-rules
11
+ */
12
+
13
+ /**
14
+ * Check if a file is a test file (skip quality checks for tests).
15
+ * @param {string} filePath
16
+ * @returns {boolean}
17
+ */
18
+ function isTestFile(filePath) {
19
+ return /\.(test|spec)\.[jt]sx?$/.test(filePath) || filePath.includes('__tests__');
20
+ }
21
+
22
+ /**
23
+ * Get the line number for a character offset in content.
24
+ * @param {string} content
25
+ * @param {number} offset
26
+ * @returns {number}
27
+ */
28
+ function lineAt(content, offset) {
29
+ return content.substring(0, offset).split('\n').length;
30
+ }
31
+
32
+ /**
33
+ * Check for hardcoded URLs, IPs, and ports.
34
+ *
35
+ * @param {string} filePath - File path
36
+ * @param {string} content - File content
37
+ * @returns {Array<{severity: string, rule: string, line: number, message: string, fix: string}>}
38
+ */
39
+ function checkHardcodedUrls(filePath, content) {
40
+ if (isTestFile(filePath)) return [];
41
+ const findings = [];
42
+ const lines = content.split('\n');
43
+
44
+ for (let i = 0; i < lines.length; i++) {
45
+ const line = lines[i];
46
+ const trimmed = line.trim();
47
+
48
+ // Skip comments
49
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
50
+ continue;
51
+ }
52
+
53
+ // Hardcoded http/https URLs
54
+ const urlMatch = line.match(/['"`](https?:\/\/[^'"`]+)['"`]/);
55
+ if (urlMatch) {
56
+ findings.push({
57
+ severity: 'block',
58
+ rule: 'no-hardcoded-urls',
59
+ line: i + 1,
60
+ message: `Hardcoded URL: ${urlMatch[1]}`,
61
+ fix: 'Use process.env or config for URLs',
62
+ });
63
+ }
64
+
65
+ // Hardcoded IP addresses
66
+ const ipMatch = line.match(/['"`](\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})['"`]/);
67
+ if (ipMatch && !urlMatch) {
68
+ findings.push({
69
+ severity: 'block',
70
+ rule: 'no-hardcoded-urls',
71
+ line: i + 1,
72
+ message: `Hardcoded IP: ${ipMatch[1]}`,
73
+ fix: 'Use process.env or config for host addresses',
74
+ });
75
+ }
76
+
77
+ // Hardcoded port assignments (const port = 3000)
78
+ const portMatch = line.match(/\b(?:const|let|var)\s+port\s*=\s*(\d+)/);
79
+ if (portMatch) {
80
+ findings.push({
81
+ severity: 'block',
82
+ rule: 'no-hardcoded-urls',
83
+ line: i + 1,
84
+ message: `Hardcoded port: ${portMatch[1]}`,
85
+ fix: 'Use process.env.PORT or config',
86
+ });
87
+ }
88
+ }
89
+
90
+ return findings;
91
+ }
92
+
93
+ /**
94
+ * Check for hardcoded secrets, API keys, tokens, and passwords.
95
+ *
96
+ * @param {string} filePath - File path
97
+ * @param {string} content - File content
98
+ * @returns {Array}
99
+ */
100
+ function checkHardcodedSecrets(filePath, content) {
101
+ if (isTestFile(filePath)) return [];
102
+ const findings = [];
103
+ const lines = content.split('\n');
104
+
105
+ const secretPatterns = [
106
+ { pattern: /(?:api[_-]?key|apikey)\s*=\s*['"`]([^'"`]{8,})['"`]/i, label: 'API key' },
107
+ { pattern: /(?:password|passwd|pwd)\s*=\s*['"`]([^'"`]+)['"`]/i, label: 'password' },
108
+ { pattern: /(?:secret|token)\s*=\s*['"`]([^'"`]{8,})['"`]/i, label: 'token/secret' },
109
+ { pattern: /['"`](eyJ[A-Za-z0-9_-]+\.)/i, label: 'JWT token' },
110
+ { pattern: /['"`](sk-[a-zA-Z0-9]{20,})['"`]/, label: 'API key' },
111
+ ];
112
+
113
+ for (let i = 0; i < lines.length; i++) {
114
+ const line = lines[i];
115
+ const trimmed = line.trim();
116
+
117
+ // Skip comments and env references
118
+ if (trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
119
+ if (line.includes('process.env')) continue;
120
+
121
+ for (const { pattern, label } of secretPatterns) {
122
+ if (pattern.test(line)) {
123
+ findings.push({
124
+ severity: 'block',
125
+ rule: 'no-hardcoded-secrets',
126
+ line: i + 1,
127
+ message: `Hardcoded ${label} detected`,
128
+ fix: 'Use environment variables or a secrets manager',
129
+ });
130
+ break; // One finding per line is enough
131
+ }
132
+ }
133
+ }
134
+
135
+ return findings;
136
+ }
137
+
138
+ /**
139
+ * Check for console.log/warn/debug left in production code.
140
+ * console.error is allowed.
141
+ *
142
+ * @param {string} filePath
143
+ * @param {string} content
144
+ * @returns {Array}
145
+ */
146
+ function checkConsoleLogs(filePath, content) {
147
+ if (isTestFile(filePath)) return [];
148
+ const findings = [];
149
+ const lines = content.split('\n');
150
+
151
+ for (let i = 0; i < lines.length; i++) {
152
+ const line = lines[i];
153
+ const trimmed = line.trim();
154
+ if (trimmed.startsWith('//')) continue;
155
+
156
+ const match = line.match(/console\.(log|warn|debug|info)\s*\(/);
157
+ if (match) {
158
+ findings.push({
159
+ severity: 'warn',
160
+ rule: 'no-console-log',
161
+ line: i + 1,
162
+ message: `console.${match[1]}() found in production code`,
163
+ fix: 'Use a proper logger or remove debug output',
164
+ });
165
+ }
166
+ }
167
+
168
+ return findings;
169
+ }
170
+
171
+ /**
172
+ * Check for functions exceeding the maximum line count.
173
+ *
174
+ * @param {string} filePath
175
+ * @param {string} content
176
+ * @param {Object} [options]
177
+ * @param {number} [options.maxLines=50]
178
+ * @returns {Array}
179
+ */
180
+ function checkFunctionLength(filePath, content, options = {}) {
181
+ const maxLines = options.maxLines || 50;
182
+ const findings = [];
183
+ const lines = content.split('\n');
184
+ let braceDepth = 0;
185
+ let funcStart = -1;
186
+ let funcName = '';
187
+
188
+ for (let i = 0; i < lines.length; i++) {
189
+ const line = lines[i];
190
+
191
+ // Detect function start
192
+ if (funcStart === -1) {
193
+ const funcMatch = line.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>)\s*\{?/);
194
+ if (funcMatch) {
195
+ funcName = funcMatch[1] || funcMatch[2] || 'anonymous';
196
+ if (line.includes('{')) {
197
+ funcStart = i;
198
+ braceDepth = 0;
199
+ // Count braces on this line
200
+ for (const ch of line) {
201
+ if (ch === '{') braceDepth++;
202
+ if (ch === '}') braceDepth--;
203
+ }
204
+ if (braceDepth <= 0) {
205
+ // One-liner function
206
+ funcStart = -1;
207
+ }
208
+ continue;
209
+ }
210
+ }
211
+ }
212
+
213
+ if (funcStart >= 0) {
214
+ for (const ch of line) {
215
+ if (ch === '{') braceDepth++;
216
+ if (ch === '}') braceDepth--;
217
+ }
218
+
219
+ if (braceDepth <= 0) {
220
+ const length = i - funcStart + 1;
221
+ if (length > maxLines) {
222
+ findings.push({
223
+ severity: 'warn',
224
+ rule: 'max-function-length',
225
+ line: funcStart + 1,
226
+ message: `Function '${funcName}' is ${length} lines (max: ${maxLines})`,
227
+ fix: 'Extract helper functions to reduce complexity',
228
+ });
229
+ }
230
+ funcStart = -1;
231
+ funcName = '';
232
+ }
233
+ }
234
+ }
235
+
236
+ return findings;
237
+ }
238
+
239
+ /**
240
+ * Check if file exceeds maximum line count.
241
+ *
242
+ * @param {string} filePath
243
+ * @param {string} content
244
+ * @param {Object} [options]
245
+ * @param {number} [options.maxLines=300]
246
+ * @returns {Array}
247
+ */
248
+ function checkFileLength(filePath, content, options = {}) {
249
+ const maxLines = options.maxLines || 300;
250
+ const lineCount = content.split('\n').length;
251
+
252
+ if (lineCount > maxLines) {
253
+ return [{
254
+ severity: 'warn',
255
+ rule: 'max-file-length',
256
+ line: 1,
257
+ message: `File is ${lineCount} lines (max: ${maxLines})`,
258
+ fix: 'Split into smaller, focused modules',
259
+ }];
260
+ }
261
+ return [];
262
+ }
263
+
264
+ /**
265
+ * Check for TODO/FIXME/HACK comments without issue references.
266
+ * Valid references: (#123), [JIRA-456], (PROJ-789)
267
+ *
268
+ * @param {string} filePath
269
+ * @param {string} content
270
+ * @returns {Array}
271
+ */
272
+ function checkTodoWithoutRef(filePath, content) {
273
+ const findings = [];
274
+ const lines = content.split('\n');
275
+
276
+ for (let i = 0; i < lines.length; i++) {
277
+ const line = lines[i];
278
+ const match = line.match(/\/\/\s*(TODO|FIXME|HACK)\b/i);
279
+ if (match) {
280
+ // Check if there's an issue reference after the tag
281
+ const hasRef = /[#(\[][A-Z0-9_-]+[\])]/i.test(line);
282
+ if (!hasRef) {
283
+ findings.push({
284
+ severity: 'warn',
285
+ rule: 'todo-needs-ref',
286
+ line: i + 1,
287
+ message: `${match[1]} without issue reference`,
288
+ fix: `Add issue reference: // ${match[1]}(#123): description`,
289
+ });
290
+ }
291
+ }
292
+ }
293
+
294
+ return findings;
295
+ }
296
+
297
+ module.exports = {
298
+ checkHardcodedUrls,
299
+ checkHardcodedSecrets,
300
+ checkConsoleLogs,
301
+ checkFunctionLength,
302
+ checkFileLength,
303
+ checkTodoWithoutRef,
304
+ };
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Quality Rules Tests
3
+ *
4
+ * Detects hardcoded values, oversized functions, console.log leftovers,
5
+ * TODO without issue refs, and other code quality issues.
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+
9
+ const {
10
+ checkHardcodedUrls,
11
+ checkHardcodedSecrets,
12
+ checkConsoleLogs,
13
+ checkFunctionLength,
14
+ checkFileLength,
15
+ checkTodoWithoutRef,
16
+ } = require('./quality-rules.js');
17
+
18
+ describe('Quality Rules', () => {
19
+ describe('checkHardcodedUrls', () => {
20
+ it('detects http:// URLs in code', () => {
21
+ const findings = checkHardcodedUrls('app.js', 'const url = "http://localhost:3000";');
22
+ expect(findings).toHaveLength(1);
23
+ expect(findings[0].rule).toBe('no-hardcoded-urls');
24
+ expect(findings[0].severity).toBe('block');
25
+ expect(findings[0].fix).toBeDefined();
26
+ });
27
+
28
+ it('detects https:// URLs in code', () => {
29
+ const findings = checkHardcodedUrls('app.js', 'const api = "https://api.example.com";');
30
+ expect(findings).toHaveLength(1);
31
+ });
32
+
33
+ it('passes clean code using env vars', () => {
34
+ const findings = checkHardcodedUrls('app.js', 'const url = process.env.API_URL;');
35
+ expect(findings).toHaveLength(0);
36
+ });
37
+
38
+ it('ignores URLs in comments', () => {
39
+ const findings = checkHardcodedUrls('app.js', '// See https://docs.example.com for details');
40
+ expect(findings).toHaveLength(0);
41
+ });
42
+
43
+ it('ignores URLs in test files', () => {
44
+ const findings = checkHardcodedUrls('app.test.js', 'const url = "http://localhost:3000";');
45
+ expect(findings).toHaveLength(0);
46
+ });
47
+
48
+ it('detects hardcoded IPs', () => {
49
+ const findings = checkHardcodedUrls('app.js', 'const host = "192.168.1.100";');
50
+ expect(findings).toHaveLength(1);
51
+ expect(findings[0].rule).toBe('no-hardcoded-urls');
52
+ });
53
+
54
+ it('detects hardcoded ports', () => {
55
+ const code = 'const port = 3000;\napp.listen(port);';
56
+ const findings = checkHardcodedUrls('server.js', code);
57
+ expect(findings).toHaveLength(1);
58
+ expect(findings[0].rule).toBe('no-hardcoded-urls');
59
+ });
60
+ });
61
+
62
+ describe('checkHardcodedSecrets', () => {
63
+ it('detects API keys', () => {
64
+ const code = 'const apiKey = "sk-1234567890abcdef";';
65
+ const findings = checkHardcodedSecrets('config.js', code);
66
+ expect(findings).toHaveLength(1);
67
+ expect(findings[0].rule).toBe('no-hardcoded-secrets');
68
+ expect(findings[0].severity).toBe('block');
69
+ });
70
+
71
+ it('detects passwords in assignments', () => {
72
+ const code = 'const password = "super-secret-123";';
73
+ const findings = checkHardcodedSecrets('auth.js', code);
74
+ expect(findings).toHaveLength(1);
75
+ });
76
+
77
+ it('detects tokens', () => {
78
+ const code = 'const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test";';
79
+ const findings = checkHardcodedSecrets('api.js', code);
80
+ expect(findings).toHaveLength(1);
81
+ });
82
+
83
+ it('passes env var references', () => {
84
+ const code = 'const apiKey = process.env.API_KEY;';
85
+ const findings = checkHardcodedSecrets('config.js', code);
86
+ expect(findings).toHaveLength(0);
87
+ });
88
+
89
+ it('ignores test files', () => {
90
+ const code = 'const token = "test-token-12345";';
91
+ const findings = checkHardcodedSecrets('auth.test.js', code);
92
+ expect(findings).toHaveLength(0);
93
+ });
94
+ });
95
+
96
+ describe('checkConsoleLogs', () => {
97
+ it('detects console.log in production code', () => {
98
+ const findings = checkConsoleLogs('app.js', 'console.log("debug");');
99
+ expect(findings).toHaveLength(1);
100
+ expect(findings[0].rule).toBe('no-console-log');
101
+ expect(findings[0].severity).toBe('warn');
102
+ });
103
+
104
+ it('detects console.warn', () => {
105
+ const findings = checkConsoleLogs('app.js', 'console.warn("oops");');
106
+ expect(findings).toHaveLength(1);
107
+ });
108
+
109
+ it('detects console.debug', () => {
110
+ const findings = checkConsoleLogs('app.js', 'console.debug("trace");');
111
+ expect(findings).toHaveLength(1);
112
+ });
113
+
114
+ it('allows console.error', () => {
115
+ const findings = checkConsoleLogs('app.js', 'console.error("critical failure");');
116
+ expect(findings).toHaveLength(0);
117
+ });
118
+
119
+ it('ignores test files', () => {
120
+ const findings = checkConsoleLogs('app.test.js', 'console.log("test output");');
121
+ expect(findings).toHaveLength(0);
122
+ });
123
+ });
124
+
125
+ describe('checkFunctionLength', () => {
126
+ it('detects functions over 50 lines', () => {
127
+ const lines = ['function longFunc() {'];
128
+ for (let i = 0; i < 55; i++) {
129
+ lines.push(` const x${i} = ${i};`);
130
+ }
131
+ lines.push('}');
132
+ const findings = checkFunctionLength('app.js', lines.join('\n'));
133
+ expect(findings).toHaveLength(1);
134
+ expect(findings[0].rule).toBe('max-function-length');
135
+ expect(findings[0].severity).toBe('warn');
136
+ expect(findings[0].message).toContain('longFunc');
137
+ });
138
+
139
+ it('passes short functions', () => {
140
+ const code = 'function short() {\n return 1;\n}';
141
+ const findings = checkFunctionLength('app.js', code);
142
+ expect(findings).toHaveLength(0);
143
+ });
144
+
145
+ it('detects arrow functions over limit', () => {
146
+ const lines = ['const longArrow = () => {'];
147
+ for (let i = 0; i < 55; i++) {
148
+ lines.push(` const x${i} = ${i};`);
149
+ }
150
+ lines.push('};');
151
+ const findings = checkFunctionLength('app.js', lines.join('\n'));
152
+ expect(findings).toHaveLength(1);
153
+ });
154
+ });
155
+
156
+ describe('checkFileLength', () => {
157
+ it('detects files over 300 lines', () => {
158
+ const lines = Array.from({ length: 310 }, (_, i) => `const x${i} = ${i};`);
159
+ const findings = checkFileLength('app.js', lines.join('\n'));
160
+ expect(findings).toHaveLength(1);
161
+ expect(findings[0].rule).toBe('max-file-length');
162
+ expect(findings[0].severity).toBe('warn');
163
+ });
164
+
165
+ it('passes files under limit', () => {
166
+ const findings = checkFileLength('app.js', 'const x = 1;');
167
+ expect(findings).toHaveLength(0);
168
+ });
169
+ });
170
+
171
+ describe('checkTodoWithoutRef', () => {
172
+ it('detects TODO without issue reference', () => {
173
+ const findings = checkTodoWithoutRef('app.js', '// TODO: fix this later');
174
+ expect(findings).toHaveLength(1);
175
+ expect(findings[0].rule).toBe('todo-needs-ref');
176
+ expect(findings[0].severity).toBe('warn');
177
+ });
178
+
179
+ it('detects FIXME without reference', () => {
180
+ const findings = checkTodoWithoutRef('app.js', '// FIXME: broken');
181
+ expect(findings).toHaveLength(1);
182
+ });
183
+
184
+ it('detects HACK without reference', () => {
185
+ const findings = checkTodoWithoutRef('app.js', '// HACK: workaround');
186
+ expect(findings).toHaveLength(1);
187
+ });
188
+
189
+ it('passes TODO with issue reference', () => {
190
+ const findings = checkTodoWithoutRef('app.js', '// TODO(#123): fix this later');
191
+ expect(findings).toHaveLength(0);
192
+ });
193
+
194
+ it('passes TODO with ticket reference', () => {
195
+ const findings = checkTodoWithoutRef('app.js', '// TODO [JIRA-456]: fix');
196
+ expect(findings).toHaveLength(0);
197
+ });
198
+ });
199
+ });