codeep 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Project-level review configuration: `.codeep/review.json`.
3
+ *
4
+ * Lets a repo extend the deterministic reviewer with its own rules, disable
5
+ * built-in rules by id, and scope which files are reviewed — all checked into
6
+ * the repo so the CLI (`codeep review`) and the GitHub Action enforce the same
7
+ * conventions with zero LLM cost. Loading is fully defensive: a missing,
8
+ * malformed, or partially-invalid config never throws — bad entries are skipped
9
+ * with a warning and the review proceeds with whatever is valid.
10
+ *
11
+ * Shape:
12
+ * {
13
+ * "rules": [
14
+ * { "id": "no-foo", "pattern": "\\bfoo\\(", "flags": "gi",
15
+ * "category": "bug", "severity": "warning",
16
+ * "message": "Avoid foo()", "suggestion": "Use bar()",
17
+ * "extensions": [".ts", ".js"] }
18
+ * ],
19
+ * "disable": ["eval-usage", "todo-comment"],
20
+ * "include": ["src/**"],
21
+ * "exclude": ["vendor/**", "dist/**"]
22
+ * }
23
+ */
24
+ import { existsSync, readFileSync } from 'fs';
25
+ import { join } from 'path';
26
+ const CONFIG_PATH = '.codeep/review.json';
27
+ const MAX_RULES = 200;
28
+ const VALID_CATEGORIES = [
29
+ 'security', 'performance', 'maintainability', 'bug', 'style', 'types', 'best-practice', 'documentation',
30
+ ];
31
+ const VALID_SEVERITIES = ['error', 'warning', 'info', 'suggestion'];
32
+ /** Convert a simple glob (`**`, `*`, `?`) into an anchored RegExp over posix paths. */
33
+ export function globToRegExp(glob) {
34
+ // Escape regex metacharacters but keep the glob wildcards * ? for translation.
35
+ // Function replacer (not `$&`) so a literal `$` in a path can't be mangled.
36
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, (m) => '\\' + m);
37
+ // Plain ASCII sentinels that won't appear in a real glob; split/join avoids
38
+ // any `$`-replacement pitfalls when substituting the regex fragments.
39
+ const DSTAR_SLASH = '__CODEEP_DSTAR_SLASH__';
40
+ const DSTAR = '__CODEEP_DSTAR__';
41
+ const body = escaped
42
+ .replace(/\*\*\//g, DSTAR_SLASH) // **/ → zero or more directory segments
43
+ .replace(/\*\*/g, DSTAR) // ** → anything, including slashes
44
+ .replace(/\*/g, '[^/]*') // * → within a single path segment
45
+ .replace(/\?/g, '[^/]') // ? → a single non-slash char
46
+ .split(DSTAR_SLASH).join('(?:.*/)?')
47
+ .split(DSTAR).join('.*');
48
+ return new RegExp('^' + body + '$');
49
+ }
50
+ function asStringArray(v) {
51
+ return Array.isArray(v)
52
+ ? v.filter((x) => typeof x === 'string' && x.length > 0)
53
+ : [];
54
+ }
55
+ export function loadReviewConfig(projectRoot) {
56
+ const filePath = join(projectRoot, CONFIG_PATH);
57
+ if (!existsSync(filePath))
58
+ return null;
59
+ let data;
60
+ try {
61
+ data = JSON.parse(readFileSync(filePath, 'utf-8'));
62
+ }
63
+ catch {
64
+ console.warn(`[codeep] Ignoring ${CONFIG_PATH}: not valid JSON.`);
65
+ return null;
66
+ }
67
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
68
+ console.warn(`[codeep] Ignoring ${CONFIG_PATH}: expected a JSON object.`);
69
+ return null;
70
+ }
71
+ const cfg = data;
72
+ const disabled = new Set(asStringArray(cfg.disable));
73
+ const include = asStringArray(cfg.include);
74
+ const exclude = asStringArray(cfg.exclude);
75
+ const rules = [];
76
+ const rawRules = Array.isArray(cfg.rules) ? cfg.rules.slice(0, MAX_RULES) : [];
77
+ for (const raw of rawRules) {
78
+ if (!raw || typeof raw !== 'object')
79
+ continue;
80
+ const r = raw;
81
+ const id = typeof r.id === 'string' && r.id.trim() ? r.id.trim() : null;
82
+ const message = typeof r.message === 'string' && r.message.trim() ? r.message.trim() : null;
83
+ const patternSrc = typeof r.pattern === 'string' && r.pattern ? r.pattern : null;
84
+ if (!id || !message || !patternSrc) {
85
+ console.warn(`[codeep] Skipping a rule in ${CONFIG_PATH}: each rule needs id, pattern and message.`);
86
+ continue;
87
+ }
88
+ // Reject oversized patterns outright — keeps regex compilation/run bounded.
89
+ if (patternSrc.length > 1000) {
90
+ console.warn(`[codeep] Skipping rule "${id}" in ${CONFIG_PATH}: pattern is too long (>1000 chars).`);
91
+ continue;
92
+ }
93
+ // Conservative ReDoS screen: reject the classic catastrophic shape — a group
94
+ // ending in an unbounded quantifier that is itself quantified, e.g. (a+)+,
95
+ // (\d*)*, (.*)+, (x+){2,}. Not exhaustive (the GitHub Action also bounds
96
+ // wall-clock), but it blocks the common foot-guns in an untrusted review.json.
97
+ if (/\([^)]*[+*]\)\s*[+*{]/.test(patternSrc)) {
98
+ console.warn(`[codeep] Skipping rule "${id}" in ${CONFIG_PATH}: nested quantifiers risk catastrophic backtracking (ReDoS).`);
99
+ continue;
100
+ }
101
+ // Always include the global flag so every match in a file is found.
102
+ let flags = typeof r.flags === 'string' && /^[gimsuy]*$/.test(r.flags) ? r.flags : '';
103
+ if (!flags.includes('g'))
104
+ flags += 'g';
105
+ let pattern;
106
+ try {
107
+ pattern = new RegExp(patternSrc, flags);
108
+ }
109
+ catch {
110
+ console.warn(`[codeep] Skipping rule "${id}" in ${CONFIG_PATH}: invalid regex.`);
111
+ continue;
112
+ }
113
+ const category = VALID_CATEGORIES.includes(r.category)
114
+ ? r.category : 'best-practice';
115
+ const severity = VALID_SEVERITIES.includes(r.severity)
116
+ ? r.severity : 'warning';
117
+ const extensions = asStringArray(r.extensions);
118
+ rules.push({
119
+ id,
120
+ pattern,
121
+ category,
122
+ severity,
123
+ message,
124
+ suggestion: typeof r.suggestion === 'string' ? r.suggestion : undefined,
125
+ extensions: extensions.length ? extensions : undefined,
126
+ });
127
+ }
128
+ return { rules, disabled, include, exclude };
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",