semlint-cli 0.1.1 → 0.1.3
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/README.md +2 -2
- package/dist/cli.js +13 -3
- package/dist/filter.js +30 -4
- package/dist/git.js +12 -26
- package/dist/init.js +19 -2
- package/dist/main.js +54 -6
- package/dist/reporter.js +35 -4
- package/package.json +3 -1
- package/rules/SEMLINT_NAMING_001.json +0 -12
- package/rules/SEMLINT_PATTERN_002.json +0 -12
- package/rules/SEMLINT_SWE_003.json +0 -12
package/README.md
CHANGED
|
@@ -115,11 +115,11 @@ This creates `./semlint.json` and auto-detects installed coding agent CLIs in th
|
|
|
115
115
|
|
|
116
116
|
If no known CLI is detected, Semlint falls back to `cursor-cli` + executable `cursor`.
|
|
117
117
|
|
|
118
|
-
Use `semlint init --force` to overwrite an existing config file.
|
|
118
|
+
Use `semlint init --force` to overwrite an existing config file. Init also creates `.semlint/rules/` and a starter rule `SEMLINT_EXAMPLE_001.json` (with a placeholder title and prompt) if they do not exist.
|
|
119
119
|
|
|
120
120
|
## Rule files
|
|
121
121
|
|
|
122
|
-
Rule JSON files are loaded from
|
|
122
|
+
Rule JSON files are loaded from `.semlint/rules/`. Run `semlint init` to create this folder and an example rule you can edit.
|
|
123
123
|
|
|
124
124
|
Required fields:
|
|
125
125
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
4
8
|
const init_1 = require("./init");
|
|
5
9
|
const main_1 = require("./main");
|
|
6
10
|
const HELP_TEXT = [
|
|
@@ -11,7 +15,7 @@ const HELP_TEXT = [
|
|
|
11
15
|
"",
|
|
12
16
|
"Commands:",
|
|
13
17
|
" check Run semantic lint rules against your git diff",
|
|
14
|
-
" init Create semlint.json
|
|
18
|
+
" init Create semlint.json, .semlint/rules/, and an example rule to edit",
|
|
15
19
|
"",
|
|
16
20
|
"Options:",
|
|
17
21
|
" -h, --help Show this help text"
|
|
@@ -122,8 +126,14 @@ async function main() {
|
|
|
122
126
|
}
|
|
123
127
|
catch (error) {
|
|
124
128
|
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
if (error instanceof HelpRequestedError) {
|
|
130
|
+
process.stderr.write(`${message}\n`);
|
|
131
|
+
process.exitCode = 0;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
process.stderr.write(picocolors_1.default.red(`Error: ${message}\n`));
|
|
135
|
+
process.exitCode = 2;
|
|
136
|
+
}
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
void main();
|
package/dist/filter.js
CHANGED
|
@@ -131,14 +131,40 @@ function buildScopedDiff(rule, fullDiff, changedFiles) {
|
|
|
131
131
|
}
|
|
132
132
|
function buildRulePrompt(rule, diff) {
|
|
133
133
|
return [
|
|
134
|
+
"You are Semlint, an expert semantic code reviewer.",
|
|
135
|
+
"Analyze ONLY the modified code present in the DIFF below.",
|
|
136
|
+
"Return JSON only (no markdown, no prose, no code fences).",
|
|
137
|
+
"Output schema:",
|
|
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
|
+
"",
|
|
134
160
|
`RULE_ID: ${rule.id}`,
|
|
135
161
|
`RULE_TITLE: ${rule.title}`,
|
|
136
162
|
`SEVERITY_DEFAULT: ${rule.effectiveSeverity}`,
|
|
137
163
|
"",
|
|
138
|
-
"DIFF:",
|
|
139
|
-
diff,
|
|
140
|
-
"",
|
|
141
164
|
"INSTRUCTIONS:",
|
|
142
|
-
rule.prompt
|
|
165
|
+
rule.prompt,
|
|
166
|
+
"",
|
|
167
|
+
"DIFF:",
|
|
168
|
+
diff
|
|
143
169
|
].join("\n");
|
|
144
170
|
}
|
package/dist/git.js
CHANGED
|
@@ -36,29 +36,6 @@ async function getGitDiff(base, head) {
|
|
|
36
36
|
const result = await runGitCommand(["diff", base, head]);
|
|
37
37
|
return result.stdout;
|
|
38
38
|
}
|
|
39
|
-
function isMissingRefError(message) {
|
|
40
|
-
return /(not a valid object name|unknown revision|bad revision|no upstream configured|no upstream branch)/i.test(message);
|
|
41
|
-
}
|
|
42
|
-
async function resolveLocalComparisonBase() {
|
|
43
|
-
const candidates = ["@{upstream}", "origin/main", "main"];
|
|
44
|
-
for (const candidate of candidates) {
|
|
45
|
-
try {
|
|
46
|
-
const result = await runGitCommand(["merge-base", "HEAD", candidate]);
|
|
47
|
-
const mergeBase = result.stdout.trim();
|
|
48
|
-
if (mergeBase !== "") {
|
|
49
|
-
return mergeBase;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
-
if (!isMissingRefError(message)) {
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return "HEAD";
|
|
61
|
-
}
|
|
62
39
|
async function getUntrackedFiles() {
|
|
63
40
|
const result = await runGitCommand(["ls-files", "--others", "--exclude-standard"]);
|
|
64
41
|
return result.stdout
|
|
@@ -70,9 +47,16 @@ async function getNoIndexDiffForFile(filePath) {
|
|
|
70
47
|
const result = await runGitCommand(["diff", "--no-index", "--", node_os_1.devNull, filePath], [0, 1]);
|
|
71
48
|
return result.stdout;
|
|
72
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the diff for the current branch limited to:
|
|
52
|
+
* - Staged changes (--cached: index vs HEAD)
|
|
53
|
+
* - Unstaged changes (working tree vs index)
|
|
54
|
+
* - Untracked files (as full-file diffs)
|
|
55
|
+
* Does not include already-committed changes on the branch.
|
|
56
|
+
*/
|
|
73
57
|
async function getLocalBranchDiff() {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
58
|
+
const stagedResult = await runGitCommand(["diff", "--cached"]);
|
|
59
|
+
const unstagedResult = await runGitCommand(["diff"]);
|
|
76
60
|
const untrackedFiles = await getUntrackedFiles();
|
|
77
61
|
const untrackedDiffChunks = [];
|
|
78
62
|
for (const filePath of untrackedFiles) {
|
|
@@ -81,5 +65,7 @@ async function getLocalBranchDiff() {
|
|
|
81
65
|
untrackedDiffChunks.push(fileDiff);
|
|
82
66
|
}
|
|
83
67
|
}
|
|
84
|
-
return [
|
|
68
|
+
return [stagedResult.stdout, unstagedResult.stdout, ...untrackedDiffChunks]
|
|
69
|
+
.filter((chunk) => chunk !== "")
|
|
70
|
+
.join("\n");
|
|
85
71
|
}
|
package/dist/init.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.scaffoldConfig = scaffoldConfig;
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -72,7 +73,23 @@ function scaffoldConfig(force = false) {
|
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
75
|
node_fs_1.default.writeFileSync(targetPath, `${JSON.stringify(scaffold, null, 2)}\n`, "utf8");
|
|
75
|
-
process.stdout.write(`Created ${targetPath}\n`);
|
|
76
|
-
process.stdout.write(`Backend setup: ${detected.backend} (${detected.reason})\n`);
|
|
76
|
+
process.stdout.write(picocolors_1.default.green(`Created ${targetPath}\n`));
|
|
77
|
+
process.stdout.write(picocolors_1.default.cyan(`Backend setup: ${detected.backend} (${detected.reason})\n`));
|
|
78
|
+
const rulesDir = node_path_1.default.join(process.cwd(), ".semlint", "rules");
|
|
79
|
+
if (!node_fs_1.default.existsSync(rulesDir)) {
|
|
80
|
+
node_fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
81
|
+
process.stdout.write(picocolors_1.default.green(`Created ${node_path_1.default.join(".semlint", "rules")}/\n`));
|
|
82
|
+
}
|
|
83
|
+
const exampleRulePath = node_path_1.default.join(rulesDir, "SEMLINT_EXAMPLE_001.json");
|
|
84
|
+
if (!node_fs_1.default.existsSync(exampleRulePath)) {
|
|
85
|
+
const exampleRule = {
|
|
86
|
+
id: "SEMLINT_EXAMPLE_001",
|
|
87
|
+
title: "My first rule",
|
|
88
|
+
severity_default: "warn",
|
|
89
|
+
prompt: "Describe what the agent should check in the changed code. Example: flag when new functions lack JSDoc, or when error handling is missing."
|
|
90
|
+
};
|
|
91
|
+
node_fs_1.default.writeFileSync(exampleRulePath, `${JSON.stringify(exampleRule, null, 2)}\n`, "utf8");
|
|
92
|
+
process.stdout.write(picocolors_1.default.green(`Created ${node_path_1.default.join(".semlint", "rules", "SEMLINT_EXAMPLE_001.json")} `) + picocolors_1.default.dim(`(edit the title and prompt to define your rule)\n`));
|
|
93
|
+
}
|
|
77
94
|
return 0;
|
|
78
95
|
}
|
package/dist/main.js
CHANGED
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runSemlint = runSemlint;
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
8
|
+
const nanospinner_1 = require("nanospinner");
|
|
7
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
10
|
const backend_1 = require("./backend");
|
|
9
11
|
const config_1 = require("./config");
|
|
@@ -15,7 +17,7 @@ const rules_1 = require("./rules");
|
|
|
15
17
|
const VERSION = "0.1.0";
|
|
16
18
|
function debugLog(enabled, message) {
|
|
17
19
|
if (enabled) {
|
|
18
|
-
process.stderr.write(`[debug] ${message}\n`);
|
|
20
|
+
process.stderr.write(`${picocolors_1.default.gray(`[debug] ${message}`)}\n`);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
function timed(enabled, label, action) {
|
|
@@ -41,10 +43,33 @@ function buildBatchPrompt(rules, diff) {
|
|
|
41
43
|
].join("\n"))
|
|
42
44
|
.join("\n\n---\n\n");
|
|
43
45
|
return [
|
|
46
|
+
"You are Semlint, an expert semantic code reviewer.",
|
|
44
47
|
"BATCH_MODE: true",
|
|
45
48
|
"Evaluate all rules below against the DIFF in one pass.",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
49
|
+
"Analyze ONLY the modified code present in the DIFF below.",
|
|
50
|
+
"Return JSON only (no markdown, no prose, no code fences).",
|
|
51
|
+
"Output schema:",
|
|
52
|
+
"{",
|
|
53
|
+
" \"diagnostics\": [",
|
|
54
|
+
" {",
|
|
55
|
+
" \"rule_id\": string,",
|
|
56
|
+
" \"severity\": \"error\" | \"warn\" | \"info\",",
|
|
57
|
+
" \"message\": string,",
|
|
58
|
+
" \"file\": string,",
|
|
59
|
+
" \"line\": number,",
|
|
60
|
+
" \"column\"?: number,",
|
|
61
|
+
" \"end_line\"?: number,",
|
|
62
|
+
" \"end_column\"?: number,",
|
|
63
|
+
" \"evidence\"?: string,",
|
|
64
|
+
" \"confidence\"?: number",
|
|
65
|
+
" }",
|
|
66
|
+
" ]",
|
|
67
|
+
"}",
|
|
68
|
+
"Rules:",
|
|
69
|
+
"- If there are no findings, return {\"diagnostics\":[]}.",
|
|
70
|
+
"- Each diagnostic must reference a changed file from the DIFF.",
|
|
71
|
+
"- rule_id must match one of the RULE_ID values listed below.",
|
|
72
|
+
"- Keep messages concise and actionable.",
|
|
48
73
|
"",
|
|
49
74
|
"RULES:",
|
|
50
75
|
ruleBlocks,
|
|
@@ -55,9 +80,10 @@ function buildBatchPrompt(rules, diff) {
|
|
|
55
80
|
}
|
|
56
81
|
async function runSemlint(options) {
|
|
57
82
|
const startedAt = Date.now();
|
|
83
|
+
let spinner = null;
|
|
58
84
|
try {
|
|
59
85
|
const config = timed(options.debug, "Loaded effective config", () => (0, config_1.loadEffectiveConfig)(options));
|
|
60
|
-
const rulesDir = node_path_1.default.join(process.cwd(), "rules");
|
|
86
|
+
const rulesDir = node_path_1.default.join(process.cwd(), ".semlint", "rules");
|
|
61
87
|
const rules = timed(config.debug, "Loaded and validated rules", () => (0, rules_1.loadRules)(rulesDir, config.rulesDisable, config.severityOverrides));
|
|
62
88
|
debugLog(config.debug, `Loaded ${rules.length} rule(s)`);
|
|
63
89
|
debugLog(config.debug, `Rule IDs: ${rules.map((rule) => rule.id).join(", ")}`);
|
|
@@ -65,7 +91,7 @@ async function runSemlint(options) {
|
|
|
65
91
|
const diff = await timedAsync(config.debug, "Computed git diff", () => useLocalBranchDiff ? (0, git_1.getLocalBranchDiff)() : (0, git_1.getGitDiff)(config.base, config.head));
|
|
66
92
|
const changedFiles = timed(config.debug, "Parsed changed files from diff", () => (0, filter_1.extractChangedFilesFromDiff)(diff));
|
|
67
93
|
debugLog(config.debug, useLocalBranchDiff
|
|
68
|
-
? "Using local branch diff (
|
|
94
|
+
? "Using local branch diff (staged + unstaged + untracked only)"
|
|
69
95
|
: `Using explicit ref diff (${config.base}..${config.head})`);
|
|
70
96
|
debugLog(config.debug, `Detected ${changedFiles.length} changed file(s)`);
|
|
71
97
|
const backend = timed(config.debug, "Initialized backend runner", () => (0, backend_1.createBackendRunner)(config));
|
|
@@ -82,6 +108,17 @@ async function runSemlint(options) {
|
|
|
82
108
|
const diagnostics = [];
|
|
83
109
|
const rulesRun = runnableRules.length;
|
|
84
110
|
let backendErrors = 0;
|
|
111
|
+
if (config.format !== "json" && rulesRun > 0) {
|
|
112
|
+
process.stdout.write(`${picocolors_1.default.bold("Running rules:")}\n`);
|
|
113
|
+
for (const rule of runnableRules) {
|
|
114
|
+
process.stdout.write(` ${picocolors_1.default.cyan(rule.id)} ${picocolors_1.default.dim(rule.title)}\n`);
|
|
115
|
+
}
|
|
116
|
+
process.stdout.write("\n");
|
|
117
|
+
}
|
|
118
|
+
spinner =
|
|
119
|
+
config.format !== "json" && rulesRun > 0
|
|
120
|
+
? (0, nanospinner_1.createSpinner)(`Analyzing ${changedFiles.length} file${changedFiles.length === 1 ? "" : "s"} with ${config.backend} in ${config.batchMode ? "batch" : "parallel"} mode...`).start()
|
|
121
|
+
: null;
|
|
85
122
|
if (config.batchMode && runnableRules.length > 0) {
|
|
86
123
|
debugLog(config.debug, `Running ${runnableRules.length} rule(s) in batch mode`);
|
|
87
124
|
const combinedDiff = timed(config.debug, "Batch: combined scoped diff build", () => runnableRules
|
|
@@ -161,12 +198,20 @@ async function runSemlint(options) {
|
|
|
161
198
|
}
|
|
162
199
|
const sorted = timed(config.debug, "Sorted diagnostics", () => (0, diagnostics_1.sortDiagnostics)(diagnostics));
|
|
163
200
|
const durationMs = Date.now() - startedAt;
|
|
201
|
+
if (spinner) {
|
|
202
|
+
if (backendErrors > 0) {
|
|
203
|
+
spinner.error({ text: "Analysis completed with backend errors" });
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
spinner.success({ text: "Analysis complete" });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
164
209
|
const outputStartedAt = Date.now();
|
|
165
210
|
if (config.format === "json") {
|
|
166
211
|
process.stdout.write(`${(0, reporter_1.formatJsonOutput)(VERSION, sorted, { rulesRun, durationMs, backendErrors })}\n`);
|
|
167
212
|
}
|
|
168
213
|
else {
|
|
169
|
-
process.stdout.write(`${(0, reporter_1.formatTextOutput)(sorted)}\n`);
|
|
214
|
+
process.stdout.write(`${(0, reporter_1.formatTextOutput)(sorted, { rulesRun, durationMs, backendErrors })}\n`);
|
|
170
215
|
}
|
|
171
216
|
debugLog(config.debug, `Rendered output in ${Date.now() - outputStartedAt}ms`);
|
|
172
217
|
debugLog(config.debug, `Total run duration ${durationMs}ms`);
|
|
@@ -179,6 +224,9 @@ async function runSemlint(options) {
|
|
|
179
224
|
return 0;
|
|
180
225
|
}
|
|
181
226
|
catch (error) {
|
|
227
|
+
if (spinner) {
|
|
228
|
+
spinner.error({ text: "Analysis failed" });
|
|
229
|
+
}
|
|
182
230
|
const message = error instanceof Error ? error.message : String(error);
|
|
183
231
|
process.stderr.write(`${message}\n`);
|
|
184
232
|
return 2;
|
package/dist/reporter.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.formatJsonOutput = formatJsonOutput;
|
|
4
7
|
exports.formatTextOutput = formatTextOutput;
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
function formatSeverity(severity) {
|
|
10
|
+
if (severity === "error")
|
|
11
|
+
return picocolors_1.default.red(severity);
|
|
12
|
+
if (severity === "warn")
|
|
13
|
+
return picocolors_1.default.yellow(severity);
|
|
14
|
+
if (severity === "info")
|
|
15
|
+
return picocolors_1.default.cyan(severity);
|
|
16
|
+
return severity;
|
|
17
|
+
}
|
|
5
18
|
function formatJsonOutput(version, diagnostics, stats) {
|
|
6
19
|
const payload = {
|
|
7
20
|
tool: {
|
|
@@ -17,7 +30,7 @@ function formatJsonOutput(version, diagnostics, stats) {
|
|
|
17
30
|
};
|
|
18
31
|
return JSON.stringify(payload, null, 2);
|
|
19
32
|
}
|
|
20
|
-
function formatTextOutput(diagnostics) {
|
|
33
|
+
function formatTextOutput(diagnostics, stats) {
|
|
21
34
|
const lines = [];
|
|
22
35
|
let currentFile = "";
|
|
23
36
|
for (const diagnostic of diagnostics) {
|
|
@@ -26,10 +39,10 @@ function formatTextOutput(diagnostics) {
|
|
|
26
39
|
lines.push("");
|
|
27
40
|
}
|
|
28
41
|
currentFile = diagnostic.file;
|
|
29
|
-
lines.push(currentFile);
|
|
42
|
+
lines.push(picocolors_1.default.underline(currentFile));
|
|
30
43
|
}
|
|
31
44
|
const column = diagnostic.column ?? 1;
|
|
32
|
-
lines.push(` ${diagnostic.line}:${column} ${diagnostic.severity} ${diagnostic.rule_id} ${diagnostic.message}`);
|
|
45
|
+
lines.push(` ${picocolors_1.default.dim(`${diagnostic.line}:${column}`)} ${formatSeverity(diagnostic.severity)} ${picocolors_1.default.gray(diagnostic.rule_id)} ${diagnostic.message}`);
|
|
33
46
|
}
|
|
34
47
|
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
35
48
|
const warnings = diagnostics.filter((d) => d.severity === "warn").length;
|
|
@@ -37,6 +50,24 @@ function formatTextOutput(diagnostics) {
|
|
|
37
50
|
if (lines.length > 0) {
|
|
38
51
|
lines.push("");
|
|
39
52
|
}
|
|
40
|
-
|
|
53
|
+
const summary = `✖ ${problems} problems (${errors} errors, ${warnings} warnings)`;
|
|
54
|
+
const timeInfo = ` in ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
55
|
+
if (problems === 0) {
|
|
56
|
+
if (stats.rulesRun === 0) {
|
|
57
|
+
lines.push(picocolors_1.default.green(`✔ 0 problems (no rules matched changed files)`));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lines.push(picocolors_1.default.green(`✔ 0 problems (0 errors, 0 warnings)`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (errors > 0) {
|
|
64
|
+
lines.push(picocolors_1.default.red(summary) + picocolors_1.default.gray(timeInfo));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
lines.push(picocolors_1.default.yellow(summary) + picocolors_1.default.gray(timeInfo));
|
|
68
|
+
}
|
|
69
|
+
if (problems === 0 && stats.durationMs > 0) {
|
|
70
|
+
lines[lines.length - 1] += picocolors_1.default.gray(timeInfo);
|
|
71
|
+
}
|
|
41
72
|
return lines.join("\n");
|
|
42
73
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semlint-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Semantic lint CLI — runs LLM-backed rules on your git diff and returns CI-friendly exit codes",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"packageManager": "pnpm@10.29.2",
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"nanospinner": "^1.2.2",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
43
45
|
"picomatch": "^4.0.3"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "SEMLINT_NAMING_001",
|
|
3
|
-
"title": "Ambient naming convention consistency",
|
|
4
|
-
"severity_default": "warn",
|
|
5
|
-
"include_globs": ["src/**/*.ts"],
|
|
6
|
-
"exclude_globs": ["**/*.test.ts", "**/*.spec.ts"],
|
|
7
|
-
"diff_regex": [
|
|
8
|
-
"^[+-].*\\b(const|let|var|function|class|interface|type|enum)\\b",
|
|
9
|
-
"^[+-].*\\b[A-Za-z_][A-Za-z0-9_]*\\b"
|
|
10
|
-
],
|
|
11
|
-
"prompt": "You are Semlint. Review ONLY the modified code in the provided DIFF and verify naming is consistent with the ambient naming conventions already used in surrounding code. Focus on identifier styles such as camelCase for variables/functions, PascalCase for classes/types/interfaces, and ALL_CAPS for constants where that convention is clearly established nearby. Flag only clear inconsistencies in newly added or renamed identifiers. Ignore untouched legacy naming unless directly impacted by the change. Return valid JSON only with shape {\"diagnostics\":[...]} and each diagnostic must include: rule_id, severity, message, file, line."
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "SEMLINT_PATTERN_002",
|
|
3
|
-
"title": "Ambient pattern is respected",
|
|
4
|
-
"severity_default": "warn",
|
|
5
|
-
"include_globs": ["src/**/*.ts"],
|
|
6
|
-
"exclude_globs": ["**/*.test.ts", "**/*.spec.ts"],
|
|
7
|
-
"diff_regex": [
|
|
8
|
-
"^[+-].*\\b(async|await|Promise|try|catch|throw|switch|map|filter|reduce|forEach)\\b",
|
|
9
|
-
"^[+-].*\\b(import|export|class|interface|type|function|return)\\b"
|
|
10
|
-
],
|
|
11
|
-
"prompt": "You are Semlint. Review ONLY the modified code in the provided DIFF and check whether ambient implementation patterns are respected. Compare new or changed code against nearby established patterns for control flow, async handling, error propagation, data transformation, module boundaries, and function/class structure. Flag clear regressions where the proposed change deviates from consistent local patterns without obvious justification. Ignore untouched legacy code and acceptable intentional improvements. Return valid JSON only with shape {\"diagnostics\":[...]} and each diagnostic must include: rule_id, severity, message, file, line."
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "SEMLINT_SWE_003",
|
|
3
|
-
"title": "Obvious SWE mistakes",
|
|
4
|
-
"severity_default": "warn",
|
|
5
|
-
"include_globs": ["src/**/*.ts"],
|
|
6
|
-
"exclude_globs": ["**/*.test.ts", "**/*.spec.ts"],
|
|
7
|
-
"diff_regex": [
|
|
8
|
-
"^[+-].*\\b(any|as\\s+any|TODO|FIXME|console\\.log|@ts-ignore|throw\\s+new\\s+Error|catch\\s*\\()\\b",
|
|
9
|
-
"^[+-].*\\b(if|else|switch|return|await|Promise|map|forEach|reduce)\\b"
|
|
10
|
-
],
|
|
11
|
-
"prompt": "You are Semlint. Review ONLY the modified code in the DIFF and find obvious software-engineering mistakes in the proposed change. Focus on clear issues such as dead code, side-effect misuse, swallowed errors, unsafe any-casts, accidental debug leftovers, contradictory conditions, and obvious maintainability hazards that are likely unintended (these are only examples, there are many more). Do not nitpick style or architecture unless the issue is clearly harmful. Report only high-signal findings tied to changed lines. Return valid JSON only with shape {\"diagnostics\":[...]} and each diagnostic must include: rule_id, severity, message, file, line."
|
|
12
|
-
}
|