semlint-cli 0.1.5 → 0.1.7
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.
- package/.semlint/rules/SEMLINT_NAMING_001.json +12 -0
- package/.semlint/rules/SEMLINT_PATTERN_002.json +12 -0
- package/.semlint/rules/SEMLINT_SWE_003.json +12 -0
- package/README.md +81 -12
- package/dist/adapters.js +27 -0
- package/dist/backend.js +103 -67
- package/dist/config.js +94 -25
- package/dist/diagnostics.js +23 -27
- package/dist/diff.js +54 -0
- package/dist/dispatch.js +94 -0
- package/dist/filter.js +16 -95
- package/dist/init.js +54 -14
- package/dist/main.js +58 -143
- package/dist/prompts.js +46 -0
- package/dist/reporter.js +12 -13
- package/dist/rules.js +11 -13
- package/dist/secrets.js +153 -0
- package/dist/secrets.test.js +83 -0
- package/dist/test2.js +5 -0
- package/dist/utils.js +18 -0
- package/package.json +4 -2
- package/prompts/batch.md +16 -0
- package/prompts/common-contract.md +20 -0
- package/prompts/retry-json.md +5 -0
- package/prompts/rule.md +16 -0
package/dist/diff.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDiffGitHeader = parseDiffGitHeader;
|
|
4
|
+
exports.splitDiffIntoFileChunks = splitDiffIntoFileChunks;
|
|
5
|
+
function unquoteDiffPath(raw) {
|
|
6
|
+
if (raw.startsWith("\"") && raw.endsWith("\"") && raw.length >= 2) {
|
|
7
|
+
return raw.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
8
|
+
}
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
function parseDiffGitHeader(line) {
|
|
12
|
+
const match = line.match(/^diff --git (?:"a\/((?:[^"\\]|\\.)+)"|a\/(\S+)) (?:"b\/((?:[^"\\]|\\.)+)"|b\/(\S+))$/);
|
|
13
|
+
if (!match) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const aRaw = match[1] ?? match[2];
|
|
17
|
+
const bRaw = match[3] ?? match[4];
|
|
18
|
+
if (!aRaw || !bRaw) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
aPath: unquoteDiffPath(aRaw),
|
|
23
|
+
bPath: unquoteDiffPath(bRaw)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function splitDiffIntoFileChunks(diff) {
|
|
27
|
+
const lines = diff.split("\n");
|
|
28
|
+
const chunks = [];
|
|
29
|
+
let currentLines = [];
|
|
30
|
+
let currentFile = "";
|
|
31
|
+
const flush = () => {
|
|
32
|
+
if (currentLines.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
chunks.push({
|
|
36
|
+
file: currentFile,
|
|
37
|
+
chunk: currentLines.join("\n")
|
|
38
|
+
});
|
|
39
|
+
currentLines = [];
|
|
40
|
+
currentFile = "";
|
|
41
|
+
};
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
if (line.startsWith("diff --git ")) {
|
|
44
|
+
flush();
|
|
45
|
+
const parsed = parseDiffGitHeader(line);
|
|
46
|
+
if (parsed) {
|
|
47
|
+
currentFile = parsed.bPath;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
currentLines.push(line);
|
|
51
|
+
}
|
|
52
|
+
flush();
|
|
53
|
+
return chunks;
|
|
54
|
+
}
|
package/dist/dispatch.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runBatchDispatch = runBatchDispatch;
|
|
4
|
+
exports.runParallelDispatch = runParallelDispatch;
|
|
5
|
+
const diagnostics_1 = require("./diagnostics");
|
|
6
|
+
const filter_1 = require("./filter");
|
|
7
|
+
const prompts_1 = require("./prompts");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
function buildBatchPrompt(rules, diff) {
|
|
10
|
+
const ruleBlocks = rules
|
|
11
|
+
.map((rule) => [
|
|
12
|
+
`RULE_ID: ${rule.id}`,
|
|
13
|
+
`RULE_TITLE: ${rule.title}`,
|
|
14
|
+
`SEVERITY_DEFAULT: ${rule.effectiveSeverity}`,
|
|
15
|
+
"INSTRUCTIONS:",
|
|
16
|
+
rule.prompt
|
|
17
|
+
].join("\n"))
|
|
18
|
+
.join("\n\n---\n\n");
|
|
19
|
+
return (0, prompts_1.renderBatchPrompt)({ ruleBlocks, diff });
|
|
20
|
+
}
|
|
21
|
+
async function runBatchDispatch(input) {
|
|
22
|
+
const { rules, diff, changedFiles, backend, config, repoRoot } = input;
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
let backendErrors = 0;
|
|
25
|
+
(0, utils_1.debugLog)(config.debug, `Running ${rules.length} rule(s) in batch mode`);
|
|
26
|
+
const combinedDiff = rules
|
|
27
|
+
.map((rule) => (0, filter_1.buildScopedDiff)(rule, diff, changedFiles))
|
|
28
|
+
.filter((chunk) => chunk.trim() !== "")
|
|
29
|
+
.join("\n");
|
|
30
|
+
const batchPrompt = buildBatchPrompt(rules, combinedDiff || diff);
|
|
31
|
+
try {
|
|
32
|
+
const batchResult = await backend.runPrompt({
|
|
33
|
+
label: "Batch",
|
|
34
|
+
prompt: batchPrompt,
|
|
35
|
+
timeoutMs: config.timeoutMs
|
|
36
|
+
});
|
|
37
|
+
const groupedByRule = batchResult.diagnostics.reduce((acc, diagnostic) => {
|
|
38
|
+
if (typeof diagnostic === "object" &&
|
|
39
|
+
diagnostic !== null &&
|
|
40
|
+
!Array.isArray(diagnostic) &&
|
|
41
|
+
typeof diagnostic.rule_id === "string") {
|
|
42
|
+
const ruleId = diagnostic.rule_id;
|
|
43
|
+
acc.set(ruleId, [...(acc.get(ruleId) ?? []), diagnostic]);
|
|
44
|
+
return acc;
|
|
45
|
+
}
|
|
46
|
+
(0, utils_1.debugLog)(config.debug, "Batch: dropped diagnostic without valid rule_id");
|
|
47
|
+
return acc;
|
|
48
|
+
}, new Map());
|
|
49
|
+
const validRuleIds = new Set(rules.map((rule) => rule.id));
|
|
50
|
+
Array.from(groupedByRule.keys())
|
|
51
|
+
.filter((ruleId) => !validRuleIds.has(ruleId))
|
|
52
|
+
.forEach((ruleId) => {
|
|
53
|
+
(0, utils_1.debugLog)(config.debug, `Batch: dropped diagnostic for unknown rule_id ${ruleId}`);
|
|
54
|
+
});
|
|
55
|
+
const normalized = rules.flatMap((rule) => (0, diagnostics_1.normalizeDiagnostics)(rule.id, groupedByRule.get(rule.id) ?? [], config.debug, repoRoot));
|
|
56
|
+
diagnostics.push(...normalized);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
backendErrors += 1;
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
(0, utils_1.debugLog)(config.debug, `Batch backend error: ${message}`);
|
|
62
|
+
}
|
|
63
|
+
return { diagnostics, backendErrors };
|
|
64
|
+
}
|
|
65
|
+
async function runParallelDispatch(input) {
|
|
66
|
+
const { rules, diff, changedFiles, backend, config, repoRoot } = input;
|
|
67
|
+
(0, utils_1.debugLog)(config.debug, `Running ${rules.length} rule(s) in parallel`);
|
|
68
|
+
const runResults = await Promise.all(rules.map(async (rule) => {
|
|
69
|
+
const ruleStartedAt = Date.now();
|
|
70
|
+
(0, utils_1.debugLog)(config.debug, `Rule ${rule.id}: started`);
|
|
71
|
+
const scopedDiff = (0, filter_1.buildScopedDiff)(rule, diff, changedFiles);
|
|
72
|
+
const prompt = (0, filter_1.buildRulePrompt)(rule, scopedDiff);
|
|
73
|
+
try {
|
|
74
|
+
const result = await backend.runRule({
|
|
75
|
+
ruleId: rule.id,
|
|
76
|
+
prompt,
|
|
77
|
+
timeoutMs: config.timeoutMs
|
|
78
|
+
});
|
|
79
|
+
const normalized = (0, diagnostics_1.normalizeDiagnostics)(rule.id, result.diagnostics, config.debug, repoRoot);
|
|
80
|
+
(0, utils_1.debugLog)(config.debug, `Rule ${rule.id}: finished in ${Date.now() - ruleStartedAt}ms`);
|
|
81
|
+
return { backendError: false, normalized };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
(0, utils_1.debugLog)(config.debug, `Backend error for rule ${rule.id}: ${message}`);
|
|
86
|
+
(0, utils_1.debugLog)(config.debug, `Rule ${rule.id}: finished in ${Date.now() - ruleStartedAt}ms`);
|
|
87
|
+
return { backendError: true, normalized: [] };
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
return {
|
|
91
|
+
diagnostics: runResults.flatMap((result) => result.normalized),
|
|
92
|
+
backendErrors: runResults.filter((result) => result.backendError).length
|
|
93
|
+
};
|
|
94
|
+
}
|
package/dist/filter.js
CHANGED
|
@@ -8,28 +8,9 @@ exports.getRuleCandidateFiles = getRuleCandidateFiles;
|
|
|
8
8
|
exports.shouldRunRule = shouldRunRule;
|
|
9
9
|
exports.buildScopedDiff = buildScopedDiff;
|
|
10
10
|
exports.buildRulePrompt = buildRulePrompt;
|
|
11
|
+
const diff_1 = require("./diff");
|
|
11
12
|
const picomatch_1 = __importDefault(require("picomatch"));
|
|
12
|
-
|
|
13
|
-
if (raw.startsWith("\"") && raw.endsWith("\"") && raw.length >= 2) {
|
|
14
|
-
return raw.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
15
|
-
}
|
|
16
|
-
return raw;
|
|
17
|
-
}
|
|
18
|
-
function parseDiffGitHeader(line) {
|
|
19
|
-
const match = line.match(/^diff --git (?:"a\/((?:[^"\\]|\\.)+)"|a\/(\S+)) (?:"b\/((?:[^"\\]|\\.)+)"|b\/(\S+))$/);
|
|
20
|
-
if (!match) {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
const aRaw = match[1] ?? match[2];
|
|
24
|
-
const bRaw = match[3] ?? match[4];
|
|
25
|
-
if (!aRaw || !bRaw) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
aPath: unquoteDiffPath(aRaw),
|
|
30
|
-
bPath: unquoteDiffPath(bRaw)
|
|
31
|
-
};
|
|
32
|
-
}
|
|
13
|
+
const prompts_1 = require("./prompts");
|
|
33
14
|
function extractChangedFilesFromDiff(diff) {
|
|
34
15
|
const files = new Set();
|
|
35
16
|
const lines = diff.split("\n");
|
|
@@ -37,7 +18,7 @@ function extractChangedFilesFromDiff(diff) {
|
|
|
37
18
|
if (!line.startsWith("diff --git ")) {
|
|
38
19
|
continue;
|
|
39
20
|
}
|
|
40
|
-
const parsed = parseDiffGitHeader(line);
|
|
21
|
+
const parsed = (0, diff_1.parseDiffGitHeader)(line);
|
|
41
22
|
if (!parsed) {
|
|
42
23
|
continue;
|
|
43
24
|
}
|
|
@@ -48,9 +29,6 @@ function extractChangedFilesFromDiff(diff) {
|
|
|
48
29
|
}
|
|
49
30
|
return Array.from(files);
|
|
50
31
|
}
|
|
51
|
-
function matchesAnyGlob(filePath, globs) {
|
|
52
|
-
return globs.some((glob) => (0, picomatch_1.default)(glob)(filePath));
|
|
53
|
-
}
|
|
54
32
|
function matchesAnyRegex(diff, regexes) {
|
|
55
33
|
for (const candidate of regexes) {
|
|
56
34
|
try {
|
|
@@ -67,14 +45,16 @@ function matchesAnyRegex(diff, regexes) {
|
|
|
67
45
|
}
|
|
68
46
|
function getRuleCandidateFiles(rule, changedFiles) {
|
|
69
47
|
let fileCandidates = changedFiles;
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
const includeMatcher = rule.include_globs && rule.include_globs.length > 0 ? (0, picomatch_1.default)(rule.include_globs) : null;
|
|
49
|
+
const excludeMatcher = rule.exclude_globs && rule.exclude_globs.length > 0 ? (0, picomatch_1.default)(rule.exclude_globs) : null;
|
|
50
|
+
if (includeMatcher) {
|
|
51
|
+
fileCandidates = changedFiles.filter((filePath) => includeMatcher(filePath));
|
|
72
52
|
if (fileCandidates.length === 0) {
|
|
73
53
|
return [];
|
|
74
54
|
}
|
|
75
55
|
}
|
|
76
|
-
if (
|
|
77
|
-
fileCandidates = fileCandidates.filter((filePath) => !
|
|
56
|
+
if (excludeMatcher) {
|
|
57
|
+
fileCandidates = fileCandidates.filter((filePath) => !excludeMatcher(filePath));
|
|
78
58
|
}
|
|
79
59
|
return fileCandidates;
|
|
80
60
|
}
|
|
@@ -88,41 +68,12 @@ function shouldRunRule(rule, changedFiles, diff) {
|
|
|
88
68
|
}
|
|
89
69
|
return true;
|
|
90
70
|
}
|
|
91
|
-
function splitDiffIntoFileChunks(diff) {
|
|
92
|
-
const lines = diff.split("\n");
|
|
93
|
-
const chunks = [];
|
|
94
|
-
let currentLines = [];
|
|
95
|
-
let currentFile = "";
|
|
96
|
-
const flush = () => {
|
|
97
|
-
if (currentLines.length === 0) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
chunks.push({
|
|
101
|
-
file: currentFile,
|
|
102
|
-
chunk: currentLines.join("\n")
|
|
103
|
-
});
|
|
104
|
-
currentLines = [];
|
|
105
|
-
currentFile = "";
|
|
106
|
-
};
|
|
107
|
-
for (const line of lines) {
|
|
108
|
-
if (line.startsWith("diff --git ")) {
|
|
109
|
-
flush();
|
|
110
|
-
const parsed = parseDiffGitHeader(line);
|
|
111
|
-
if (parsed) {
|
|
112
|
-
currentFile = parsed.bPath;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
currentLines.push(line);
|
|
116
|
-
}
|
|
117
|
-
flush();
|
|
118
|
-
return chunks;
|
|
119
|
-
}
|
|
120
71
|
function buildScopedDiff(rule, fullDiff, changedFiles) {
|
|
121
72
|
const candidateFiles = new Set(getRuleCandidateFiles(rule, changedFiles));
|
|
122
73
|
if (candidateFiles.size === 0) {
|
|
123
74
|
return fullDiff;
|
|
124
75
|
}
|
|
125
|
-
const chunks = splitDiffIntoFileChunks(fullDiff);
|
|
76
|
+
const chunks = (0, diff_1.splitDiffIntoFileChunks)(fullDiff);
|
|
126
77
|
const scoped = chunks
|
|
127
78
|
.filter((chunk) => chunk.file !== "" && candidateFiles.has(chunk.file))
|
|
128
79
|
.map((chunk) => chunk.chunk)
|
|
@@ -130,41 +81,11 @@ function buildScopedDiff(rule, fullDiff, changedFiles) {
|
|
|
130
81
|
return scoped.trim() === "" ? fullDiff : scoped;
|
|
131
82
|
}
|
|
132
83
|
function buildRulePrompt(rule, diff) {
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"{",
|
|
139
|
-
" \"diagnostics\": [",
|
|
140
|
-
" {",
|
|
141
|
-
" \"rule_id\": string,",
|
|
142
|
-
" \"severity\": \"error\" | \"warn\" | \"info\",",
|
|
143
|
-
" \"message\": string,",
|
|
144
|
-
" \"file\": string,",
|
|
145
|
-
" \"line\": number,",
|
|
146
|
-
" \"column\"?: number,",
|
|
147
|
-
" \"end_line\"?: number,",
|
|
148
|
-
" \"end_column\"?: number,",
|
|
149
|
-
" \"evidence\"?: string,",
|
|
150
|
-
" \"confidence\"?: number",
|
|
151
|
-
" }",
|
|
152
|
-
" ]",
|
|
153
|
-
"}",
|
|
154
|
-
"Rules:",
|
|
155
|
-
"- If there are no findings, return {\"diagnostics\":[]}.",
|
|
156
|
-
"- Each diagnostic must reference a changed file from the DIFF.",
|
|
157
|
-
"- Use the provided RULE_ID exactly in every diagnostic.",
|
|
158
|
-
"- Keep messages concise and actionable.",
|
|
159
|
-
"",
|
|
160
|
-
`RULE_ID: ${rule.id}`,
|
|
161
|
-
`RULE_TITLE: ${rule.title}`,
|
|
162
|
-
`SEVERITY_DEFAULT: ${rule.effectiveSeverity}`,
|
|
163
|
-
"",
|
|
164
|
-
"INSTRUCTIONS:",
|
|
165
|
-
rule.prompt,
|
|
166
|
-
"",
|
|
167
|
-
"DIFF:",
|
|
84
|
+
return (0, prompts_1.renderRulePrompt)({
|
|
85
|
+
ruleId: rule.id,
|
|
86
|
+
ruleTitle: rule.title,
|
|
87
|
+
severityDefault: rule.effectiveSeverity,
|
|
88
|
+
instructions: rule.prompt,
|
|
168
89
|
diff
|
|
169
|
-
|
|
90
|
+
});
|
|
170
91
|
}
|
package/dist/init.js
CHANGED
|
@@ -8,6 +8,23 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
8
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const SCAFFOLD_BACKENDS = {
|
|
12
|
+
"cursor-cli": {
|
|
13
|
+
executable: "cursor",
|
|
14
|
+
args: ["agent", "{prompt}", "--model", "{model}", "--print", "--mode", "ask", "--output-format", "text"],
|
|
15
|
+
model: "auto"
|
|
16
|
+
},
|
|
17
|
+
"claude-code": {
|
|
18
|
+
executable: "claude",
|
|
19
|
+
args: ["{prompt}", "--model", "{model}", "--output-format", "json"],
|
|
20
|
+
model: "auto"
|
|
21
|
+
},
|
|
22
|
+
"codex-cli": {
|
|
23
|
+
executable: "codex",
|
|
24
|
+
args: ["{prompt}", "--model", "{model}"],
|
|
25
|
+
model: "auto"
|
|
26
|
+
}
|
|
27
|
+
};
|
|
11
28
|
function commandExists(command) {
|
|
12
29
|
const result = (0, node_child_process_1.spawnSync)(command, ["--version"], {
|
|
13
30
|
stdio: "ignore"
|
|
@@ -34,12 +51,21 @@ function detectBackend() {
|
|
|
34
51
|
];
|
|
35
52
|
for (const candidate of candidates) {
|
|
36
53
|
if (commandExists(candidate.executable)) {
|
|
37
|
-
|
|
54
|
+
const scaffold = SCAFFOLD_BACKENDS[candidate.backend];
|
|
55
|
+
return {
|
|
56
|
+
backend: candidate.backend,
|
|
57
|
+
executable: scaffold.executable,
|
|
58
|
+
args: scaffold.args,
|
|
59
|
+
model: scaffold.model,
|
|
60
|
+
reason: candidate.reason
|
|
61
|
+
};
|
|
38
62
|
}
|
|
39
63
|
}
|
|
40
64
|
return {
|
|
41
65
|
backend: "cursor-cli",
|
|
42
|
-
executable: "cursor",
|
|
66
|
+
executable: SCAFFOLD_BACKENDS["cursor-cli"].executable,
|
|
67
|
+
args: SCAFFOLD_BACKENDS["cursor-cli"].args,
|
|
68
|
+
model: SCAFFOLD_BACKENDS["cursor-cli"].model,
|
|
43
69
|
reason: "no known agent CLI detected, using default Cursor setup"
|
|
44
70
|
};
|
|
45
71
|
}
|
|
@@ -52,7 +78,6 @@ function scaffoldConfig(force = false) {
|
|
|
52
78
|
const detected = detectBackend();
|
|
53
79
|
const scaffold = {
|
|
54
80
|
backend: detected.backend,
|
|
55
|
-
model: "auto",
|
|
56
81
|
budgets: {
|
|
57
82
|
timeout_ms: 120000
|
|
58
83
|
},
|
|
@@ -62,13 +87,21 @@ function scaffoldConfig(force = false) {
|
|
|
62
87
|
execution: {
|
|
63
88
|
batch: false
|
|
64
89
|
},
|
|
90
|
+
security: {
|
|
91
|
+
secret_guard: true,
|
|
92
|
+
allow_patterns: [],
|
|
93
|
+
ignore_files: [".gitignore", ".cursorignore", ".semlintignore"],
|
|
94
|
+
allow_files: []
|
|
95
|
+
},
|
|
65
96
|
rules: {
|
|
66
97
|
disable: [],
|
|
67
98
|
severity_overrides: {}
|
|
68
99
|
},
|
|
69
100
|
backends: {
|
|
70
101
|
[detected.backend]: {
|
|
71
|
-
executable: detected.executable
|
|
102
|
+
executable: detected.executable,
|
|
103
|
+
args: detected.args,
|
|
104
|
+
model: detected.model
|
|
72
105
|
}
|
|
73
106
|
}
|
|
74
107
|
};
|
|
@@ -80,16 +113,23 @@ function scaffoldConfig(force = false) {
|
|
|
80
113
|
node_fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
81
114
|
process.stdout.write(picocolors_1.default.green(`Created ${node_path_1.default.join(".semlint", "rules")}/\n`));
|
|
82
115
|
}
|
|
83
|
-
const
|
|
84
|
-
if (!node_fs_1.default.existsSync(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
const bundledRulesDir = node_path_1.default.resolve(__dirname, "..", ".semlint", "rules");
|
|
117
|
+
if (!node_fs_1.default.existsSync(bundledRulesDir) || !node_fs_1.default.statSync(bundledRulesDir).isDirectory()) {
|
|
118
|
+
process.stderr.write(picocolors_1.default.yellow(`No bundled rules found at ${bundledRulesDir}. Add rule files manually under ${node_path_1.default.join(".semlint", "rules")}.\n`));
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
const bundledRules = node_fs_1.default
|
|
122
|
+
.readdirSync(bundledRulesDir)
|
|
123
|
+
.filter((name) => name.endsWith(".json"))
|
|
124
|
+
.sort((a, b) => a.localeCompare(b));
|
|
125
|
+
for (const fileName of bundledRules) {
|
|
126
|
+
const source = node_path_1.default.join(bundledRulesDir, fileName);
|
|
127
|
+
const target = node_path_1.default.join(rulesDir, fileName);
|
|
128
|
+
if (!force && node_fs_1.default.existsSync(target)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
node_fs_1.default.copyFileSync(source, target);
|
|
132
|
+
process.stdout.write(picocolors_1.default.green(`Copied ${node_path_1.default.join(".semlint", "rules", fileName)}\n`));
|
|
93
133
|
}
|
|
94
134
|
return 0;
|
|
95
135
|
}
|