semlint-cli 0.1.3 → 0.1.5

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.
@@ -12,8 +12,13 @@ const VALID_SEVERITIES = new Set(["error", "warn", "info"]);
12
12
  function isPositiveInteger(value) {
13
13
  return typeof value === "number" && Number.isInteger(value) && value >= 1;
14
14
  }
15
- function normalizeDiagnostics(ruleId, diagnostics, debug) {
15
+ /**
16
+ * @param resolveRoot - Directory to resolve diagnostic file paths against (e.g. git repo root).
17
+ * Git diff paths are repo-relative; resolving from repo root ensures paths exist when running from subdirs.
18
+ */
19
+ function normalizeDiagnostics(ruleId, diagnostics, debug, resolveRoot) {
16
20
  const out = [];
21
+ const baseDir = resolveRoot && resolveRoot.length > 0 ? resolveRoot : process.cwd();
17
22
  for (const raw of diagnostics) {
18
23
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
19
24
  if (debug) {
@@ -41,7 +46,7 @@ function normalizeDiagnostics(ruleId, diagnostics, debug) {
41
46
  }
42
47
  continue;
43
48
  }
44
- const absolute = node_path_1.default.resolve(file);
49
+ const absolute = node_path_1.default.resolve(baseDir, file);
45
50
  if (!node_fs_1.default.existsSync(absolute)) {
46
51
  if (debug) {
47
52
  process.stderr.write(`[debug] Dropped diagnostic for ${ruleId}: file does not exist (${file})\n`);
package/dist/git.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getGitDiff = getGitDiff;
4
+ exports.getRepoRoot = getRepoRoot;
4
5
  exports.getLocalBranchDiff = getLocalBranchDiff;
5
6
  const node_child_process_1 = require("node:child_process");
6
7
  const node_os_1 = require("node:os");
@@ -36,6 +37,20 @@ async function getGitDiff(base, head) {
36
37
  const result = await runGitCommand(["diff", base, head]);
37
38
  return result.stdout;
38
39
  }
40
+ /**
41
+ * Returns the absolute path of the git repository root, or null if not in a git repo.
42
+ * Used to resolve diagnostic file paths, which are always repo-relative in git diff output.
43
+ */
44
+ async function getRepoRoot() {
45
+ try {
46
+ const result = await runGitCommand(["rev-parse", "--show-toplevel"]);
47
+ const root = result.stdout.trim();
48
+ return root !== "" ? root : null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
39
54
  async function getUntrackedFiles() {
40
55
  const result = await runGitCommand(["ls-files", "--others", "--exclude-standard"]);
41
56
  return result.stdout
package/dist/main.js CHANGED
@@ -70,6 +70,8 @@ function buildBatchPrompt(rules, diff) {
70
70
  "- Each diagnostic must reference a changed file from the DIFF.",
71
71
  "- rule_id must match one of the RULE_ID values listed below.",
72
72
  "- Keep messages concise and actionable.",
73
+ "- Deduplicate semantically equivalent findings before returning output.",
74
+ "- When duplicates exist, keep a single diagnostic from the rule that semantically matches the issue best, even if that selected diagnostic has lower severity.",
73
75
  "",
74
76
  "RULES:",
75
77
  ruleBlocks,
@@ -94,6 +96,7 @@ async function runSemlint(options) {
94
96
  ? "Using local branch diff (staged + unstaged + untracked only)"
95
97
  : `Using explicit ref diff (${config.base}..${config.head})`);
96
98
  debugLog(config.debug, `Detected ${changedFiles.length} changed file(s)`);
99
+ const repoRoot = await timedAsync(config.debug, "Resolved git repo root", () => (0, git_1.getRepoRoot)());
97
100
  const backend = timed(config.debug, "Initialized backend runner", () => (0, backend_1.createBackendRunner)(config));
98
101
  const runnableRules = rules.filter((rule) => {
99
102
  const filterStartedAt = Date.now();
@@ -154,7 +157,7 @@ async function runSemlint(options) {
154
157
  }
155
158
  }
156
159
  for (const rule of runnableRules) {
157
- const normalized = timed(config.debug, `Batch: diagnostics normalization for ${rule.id}`, () => (0, diagnostics_1.normalizeDiagnostics)(rule.id, groupedByRule.get(rule.id) ?? [], config.debug));
160
+ const normalized = timed(config.debug, `Batch: diagnostics normalization for ${rule.id}`, () => (0, diagnostics_1.normalizeDiagnostics)(rule.id, groupedByRule.get(rule.id) ?? [], config.debug, repoRoot));
158
161
  diagnostics.push(...normalized);
159
162
  }
160
163
  }
@@ -179,7 +182,7 @@ async function runSemlint(options) {
179
182
  prompt,
180
183
  timeoutMs: config.timeoutMs
181
184
  }));
182
- normalized = timed(config.debug, `Rule ${rule.id}: diagnostics normalization`, () => (0, diagnostics_1.normalizeDiagnostics)(rule.id, result.diagnostics, config.debug));
185
+ normalized = timed(config.debug, `Rule ${rule.id}: diagnostics normalization`, () => (0, diagnostics_1.normalizeDiagnostics)(rule.id, result.diagnostics, config.debug, repoRoot));
183
186
  }
184
187
  catch (error) {
185
188
  backendError = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semlint-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",