@titan-design/style-checker 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henry Jewkes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @titan-design/style-checker
2
+
3
+ Runs external lint tools against configs generated from a style profile, and normalizes
4
+ their output into one diagnostic shape. A profile goes in; a ruff config and an ESLint flat
5
+ config come out; ruff and ESLint run over the files; their JSON output comes back as
6
+ `CheckDiagnostic` records with a file, line, column, severity, category and rule. It also
7
+ diffs a file's style observations against a profile, without any external tool.
8
+
9
+ Tier 2 of the titan-platform DAG (TP-135). Depends on `@titan-design/style-profile` and
10
+ `@titan-design/style-analyzer` (both tier 2; the analyzer only for the `Observation` type).
11
+ `zod` v4 is a peer because style-profile needs it at runtime. Ported unchanged from
12
+ codewatch's `@codewatch/checker`, plus `diffAgainstProfile` from codewatch's CLI.
13
+
14
+ ```sh
15
+ npm install @titan-design/style-checker zod
16
+ ```
17
+
18
+ ```ts
19
+ import { generateRuffConfig, orchestrate } from "@titan-design/style-checker";
20
+
21
+ generateRuffConfig(profile);
22
+ // { lint: { select: ["N", "D", "C90"], pydocstyle: { convention: "google" }, mccabe: { "max-complexity": 30 } }, "line-length": 100 }
23
+
24
+ const { diagnostics, summary } = await orchestrate({ profile, files: ["src/app.ts", "app.py"] });
25
+ // diagnostics: [{ file: "app.py", line: 5, column: 4, severity: "warn", category: "naming", rule: "N806", ... }]
26
+ ```
27
+
28
+ ## API
29
+
30
+ - `generateRuffConfig(profile)` returns a `RuffConfig`. It selects `N`, `I`, `D` and `C90`
31
+ for naming, import order, function docs and function length that clear the profile's
32
+ `info` threshold, and carries `line-length`, isort section order, pydocstyle convention
33
+ and mccabe max complexity.
34
+ - `generateEslintConfig(profile)` returns an `EslintFlatConfigEntry[]`: one entry for
35
+ `**/*.ts` and `**/*.tsx` holding the rules style-profile's rule builders produce, or an
36
+ empty array when there are none.
37
+ - `orchestrate({ profile, files, fix?, language? })` picks the files by extension, runs
38
+ ESLint (through `npx eslint`) on `.ts`, `.tsx`, `.js`, `.jsx` and ruff on `.py`, and
39
+ returns `{ diagnostics, summary }`. A tool is skipped when its generated config is empty.
40
+ `language` restricts it to one tool; otherwise it is detected from the file list.
41
+ - `parseEslintJsonOutput(json)` and `parseRuffJsonOutput(json)` turn each tool's
42
+ `--format json` output into `CheckDiagnostic[]`. Both throw on output that is not JSON.
43
+ - `formatDiagnostic(d)` prints `file:line:column severity message [category.rule]`.
44
+ - `diffAgainstProfile(profile, observations)` compares each observation's value with the
45
+ profile's convention for its `type` and returns `{ deviations, summary }`. Observations
46
+ whose type has no profile rule count toward `total` but neither match nor deviate.
47
+ - Types: `CheckDiagnostic`, `CheckResult`, `OrchestratorOptions`, `OrchestratorResult`,
48
+ `RuffConfig`, `EslintFlatConfigEntry`, `Deviation`, `DiffResult`, and `Severity`
49
+ (re-exported from style-profile).
50
+
51
+ Which runners fire, with which configs, is product policy. The runner seam is one
52
+ function per tool (`runEslint`, `runRuff`) over a shared `runTool` spawn helper; new
53
+ runners are added beside them.
54
+
55
+ ## Gotchas
56
+
57
+ - ESLint 9 rejects the generated flat config: it names plugin rules
58
+ (`@typescript-eslint/…`, `perfectionist/…`, `unicorn/…`, `jsdoc/…`) but loads no plugins.
59
+ The runner reads only stdout, so the failure surfaces as zero diagnostics, not an error.
60
+ - ruff's `max-complexity` is set from the profile's `functionMaxLines`, a line count, not a
61
+ cyclomatic complexity.
62
+ - Every ruff diagnostic has severity `warn`. ESLint severity 2 maps to `error`, anything
63
+ else to `warn`. Nothing produces `info`.
64
+ - `summary.fixed` is always 0, even with `fix: true`.
@@ -0,0 +1,93 @@
1
+ import { Severity, Profile } from '@titan-design/style-profile';
2
+ export { Severity } from '@titan-design/style-profile';
3
+ import { Observation } from '@titan-design/style-analyzer';
4
+
5
+ interface CheckDiagnostic {
6
+ file: string;
7
+ line: number;
8
+ column: number;
9
+ severity: Exclude<Severity, "off">;
10
+ message: string;
11
+ category: string;
12
+ rule: string;
13
+ fixable: boolean;
14
+ fix?: {
15
+ range: [number, number];
16
+ text: string;
17
+ };
18
+ }
19
+ interface CheckResult {
20
+ diagnostics: CheckDiagnostic[];
21
+ tool: "eslint" | "ruff";
22
+ exitCode: number;
23
+ }
24
+ interface OrchestratorOptions {
25
+ profile: Profile;
26
+ files: string[];
27
+ fix?: boolean;
28
+ language?: "typescript" | "python";
29
+ }
30
+ interface OrchestratorResult {
31
+ diagnostics: CheckDiagnostic[];
32
+ summary: {
33
+ total: number;
34
+ errors: number;
35
+ warnings: number;
36
+ infos: number;
37
+ fixed: number;
38
+ };
39
+ }
40
+
41
+ declare function orchestrate(options: OrchestratorOptions): Promise<OrchestratorResult>;
42
+
43
+ interface EslintFlatConfigEntry {
44
+ plugins?: Record<string, unknown>;
45
+ rules?: Record<string, unknown>;
46
+ files?: string[];
47
+ }
48
+ declare function generateEslintConfig(profile: Profile): EslintFlatConfigEntry[];
49
+
50
+ interface RuffConfig {
51
+ lint?: {
52
+ select?: string[];
53
+ ignore?: string[];
54
+ fixable?: string[];
55
+ "per-file-ignores"?: Record<string, string[]>;
56
+ mccabe?: {
57
+ "max-complexity"?: number;
58
+ };
59
+ pydocstyle?: {
60
+ convention?: string;
61
+ };
62
+ isort?: {
63
+ "known-first-party"?: string[];
64
+ "section-order"?: string[];
65
+ };
66
+ };
67
+ "line-length"?: number;
68
+ }
69
+ declare function generateRuffConfig(profile: Profile): RuffConfig;
70
+
71
+ declare function parseEslintJsonOutput(jsonStr: string): CheckDiagnostic[];
72
+ declare function parseRuffJsonOutput(jsonStr: string): CheckDiagnostic[];
73
+ declare function formatDiagnostic(d: CheckDiagnostic): string;
74
+
75
+ interface Deviation {
76
+ file: string;
77
+ line: number;
78
+ rule: string;
79
+ expected: string;
80
+ found: string;
81
+ severity: "error" | "warn" | "info";
82
+ }
83
+ interface DiffResult {
84
+ deviations: Deviation[];
85
+ summary: {
86
+ total: number;
87
+ matching: number;
88
+ deviating: number;
89
+ };
90
+ }
91
+ declare function diffAgainstProfile(profile: Profile, observations: Observation[]): DiffResult;
92
+
93
+ export { type CheckDiagnostic, type CheckResult, type Deviation, type DiffResult, type EslintFlatConfigEntry, type OrchestratorOptions, type OrchestratorResult, type RuffConfig, diffAgainstProfile, formatDiagnostic, generateEslintConfig, generateRuffConfig, orchestrate, parseEslintJsonOutput, parseRuffJsonOutput };
package/dist/index.js ADDED
@@ -0,0 +1,397 @@
1
+ // src/generators/eslint.ts
2
+ import {
3
+ buildNamingConventionRule,
4
+ buildImportOrderRule,
5
+ buildFunctionLengthRule,
6
+ buildFileNamingRule,
7
+ buildJsdocRules
8
+ } from "@titan-design/style-profile";
9
+ function generateEslintConfig(profile) {
10
+ const entries = [];
11
+ const rules = {};
12
+ const namingRule = buildNamingConventionRule(profile);
13
+ if (namingRule) rules[namingRule[0]] = namingRule[1];
14
+ const importRule = buildImportOrderRule(profile);
15
+ if (importRule) rules[importRule[0]] = importRule[1];
16
+ const fnLengthRule = buildFunctionLengthRule(profile);
17
+ if (fnLengthRule) rules[fnLengthRule[0]] = fnLengthRule[1];
18
+ const fileNamingRule = buildFileNamingRule(profile);
19
+ if (fileNamingRule) rules[fileNamingRule[0]] = fileNamingRule[1];
20
+ const jsdocRules = buildJsdocRules(profile);
21
+ for (const [name, value] of jsdocRules) {
22
+ rules[name] = value;
23
+ }
24
+ if (Object.keys(rules).length > 0) {
25
+ entries.push({
26
+ files: ["**/*.ts", "**/*.tsx"],
27
+ rules
28
+ });
29
+ }
30
+ return entries;
31
+ }
32
+
33
+ // src/generators/ruff.ts
34
+ function addNaming(profile, lint) {
35
+ if (profile.naming) {
36
+ const hasNamingRules = Object.values(profile.naming).some(
37
+ (rule) => rule.confidence >= profile.severityThresholds.info
38
+ );
39
+ if (hasNamingRules) lint.select.push("N");
40
+ }
41
+ }
42
+ function addImportOrder(profile, lint) {
43
+ const importOrder = profile.structure?.importOrder;
44
+ if (importOrder && importOrder.confidence >= profile.severityThresholds.info) {
45
+ lint.select.push("I");
46
+ if (Array.isArray(importOrder.convention)) {
47
+ lint.isort = {
48
+ "section-order": importOrder.convention
49
+ };
50
+ }
51
+ }
52
+ }
53
+ function addFunctionDocs(profile, lint) {
54
+ const functionDocs = profile.documentation?.functionDocs;
55
+ if (functionDocs && functionDocs.confidence >= profile.severityThresholds.info) {
56
+ lint.select.push("D");
57
+ const conv = functionDocs.convention;
58
+ if (typeof conv === "string" && conv !== "jsdoc-selective" && conv !== "jsdoc-all") {
59
+ lint.pydocstyle = { convention: conv };
60
+ }
61
+ }
62
+ }
63
+ function addComplexity(profile, lint) {
64
+ const maxLines = profile.structure?.functionMaxLines;
65
+ if (maxLines && maxLines.confidence >= profile.severityThresholds.info && typeof maxLines.convention === "number") {
66
+ lint.select.push("C90");
67
+ lint.mccabe = { "max-complexity": maxLines.convention };
68
+ }
69
+ }
70
+ function generateRuffConfig(profile) {
71
+ const lint = { select: [] };
72
+ const config = { lint };
73
+ addNaming(profile, lint);
74
+ addImportOrder(profile, lint);
75
+ addFunctionDocs(profile, lint);
76
+ addComplexity(profile, lint);
77
+ const lineLength = profile.formatting?.lineLength;
78
+ if (lineLength && typeof lineLength.convention === "number") {
79
+ config["line-length"] = lineLength.convention;
80
+ }
81
+ return config;
82
+ }
83
+
84
+ // src/runners/eslint-runner.ts
85
+ import { writeFileSync, mkdtempSync, rmSync } from "fs";
86
+ import { join } from "path";
87
+ import { tmpdir } from "os";
88
+
89
+ // src/runners/tool-runner.ts
90
+ import { spawn } from "child_process";
91
+ function runTool(command, args, options) {
92
+ return new Promise((resolve, reject) => {
93
+ const child = spawn(command, args, {
94
+ cwd: options?.cwd,
95
+ timeout: options?.timeout ?? 6e4,
96
+ stdio: ["ignore", "pipe", "pipe"]
97
+ });
98
+ const chunks = [];
99
+ const errChunks = [];
100
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
101
+ child.stderr.on("data", (chunk) => errChunks.push(chunk));
102
+ child.on("error", (err) => {
103
+ reject(new Error(`Failed to spawn ${command}: ${err.message}`));
104
+ });
105
+ child.on("close", (code) => {
106
+ resolve({
107
+ stdout: Buffer.concat(chunks).toString("utf-8"),
108
+ stderr: Buffer.concat(errChunks).toString("utf-8"),
109
+ exitCode: code ?? 1
110
+ });
111
+ });
112
+ });
113
+ }
114
+
115
+ // src/formatters/unified.ts
116
+ var RUFF_CODE_CATEGORY = {
117
+ N: "naming",
118
+ I: "imports",
119
+ D: "documentation",
120
+ C: "complexity",
121
+ E: "formatting",
122
+ W: "formatting",
123
+ F: "errors"
124
+ };
125
+ var ESLINT_RULE_CATEGORY = {
126
+ "@typescript-eslint/naming-convention": "naming",
127
+ "unicorn/filename-case": "naming",
128
+ "perfectionist/sort-imports": "imports",
129
+ "import/order": "imports",
130
+ "max-lines-per-function": "structure",
131
+ "max-depth": "structure",
132
+ "jsdoc/require-jsdoc": "documentation"
133
+ };
134
+ function eslintSeverityToSeverity(eslintSeverity) {
135
+ return eslintSeverity === 2 ? "error" : "warn";
136
+ }
137
+ function categorizeEslintRule(ruleId) {
138
+ if (ESLINT_RULE_CATEGORY[ruleId]) return ESLINT_RULE_CATEGORY[ruleId];
139
+ if (ruleId.startsWith("@typescript-eslint/")) return "typescript";
140
+ if (ruleId.startsWith("jsdoc/")) return "documentation";
141
+ if (ruleId.startsWith("unicorn/")) return "style";
142
+ if (ruleId.startsWith("import/")) return "imports";
143
+ return "other";
144
+ }
145
+ function categorizeRuffCode(code) {
146
+ const prefix = code.replace(/[0-9]/g, "");
147
+ return RUFF_CODE_CATEGORY[prefix] ?? "other";
148
+ }
149
+ function parseEslintJsonOutput(jsonStr) {
150
+ let entries;
151
+ try {
152
+ entries = JSON.parse(jsonStr);
153
+ } catch {
154
+ throw new Error(
155
+ `Failed to parse ESLint JSON output. Raw output:
156
+ ${jsonStr.slice(0, 500)}`
157
+ );
158
+ }
159
+ const diagnostics = [];
160
+ for (const entry of entries) {
161
+ for (const msg of entry.messages) {
162
+ if (!msg.ruleId) continue;
163
+ diagnostics.push({
164
+ file: entry.filePath,
165
+ line: msg.line,
166
+ column: msg.column,
167
+ severity: eslintSeverityToSeverity(msg.severity),
168
+ message: msg.message,
169
+ category: categorizeEslintRule(msg.ruleId),
170
+ rule: msg.ruleId,
171
+ fixable: msg.fix != null,
172
+ fix: msg.fix ?? void 0
173
+ });
174
+ }
175
+ }
176
+ return diagnostics;
177
+ }
178
+ function parseRuffJsonOutput(jsonStr) {
179
+ let entries;
180
+ try {
181
+ entries = JSON.parse(jsonStr);
182
+ } catch {
183
+ throw new Error(
184
+ `Failed to parse Ruff JSON output. Raw output:
185
+ ${jsonStr.slice(0, 500)}`
186
+ );
187
+ }
188
+ return entries.map((entry) => ({
189
+ file: entry.filename,
190
+ line: entry.location.row,
191
+ column: entry.location.column,
192
+ severity: "warn",
193
+ message: entry.message,
194
+ category: categorizeRuffCode(entry.code),
195
+ rule: entry.code,
196
+ fixable: entry.fix != null
197
+ }));
198
+ }
199
+ function formatDiagnostic(d) {
200
+ return `${d.file}:${d.line}:${d.column} ${d.severity} ${d.message} [${d.category}.${d.rule}]`;
201
+ }
202
+
203
+ // src/runners/eslint-runner.ts
204
+ async function runEslint(config, files, options) {
205
+ const tempDir = mkdtempSync(join(tmpdir(), "codewatch-eslint-"));
206
+ const configPath = join(tempDir, "eslint.config.js");
207
+ try {
208
+ const configContent = `export default ${JSON.stringify(config, null, 2)};`;
209
+ writeFileSync(configPath, configContent, "utf-8");
210
+ const args = [
211
+ "--config",
212
+ configPath,
213
+ "--format",
214
+ "json",
215
+ ...options?.fix ? ["--fix"] : [],
216
+ ...files
217
+ ];
218
+ const result = await runTool("npx", ["eslint", ...args]);
219
+ const diagnostics = result.stdout.trim() ? parseEslintJsonOutput(result.stdout) : [];
220
+ return { diagnostics, exitCode: result.exitCode };
221
+ } finally {
222
+ rmSync(tempDir, { recursive: true, force: true });
223
+ }
224
+ }
225
+
226
+ // src/runners/ruff-runner.ts
227
+ import { writeFileSync as writeFileSync2, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
228
+ import { join as join2 } from "path";
229
+ import { tmpdir as tmpdir2 } from "os";
230
+ function quoteList(values) {
231
+ return `[${values.map((s) => `"${s}"`).join(", ")}]`;
232
+ }
233
+ function lintSelectionLines(lint) {
234
+ const lines = [];
235
+ if (lint.select) lines.push(`select = ${quoteList(lint.select)}`);
236
+ if (lint.ignore) lines.push(`ignore = ${quoteList(lint.ignore)}`);
237
+ return lines;
238
+ }
239
+ function lintSubsectionLines(lint) {
240
+ const lines = [];
241
+ if (lint.mccabe) {
242
+ lines.push("[lint.mccabe]");
243
+ if (lint.mccabe["max-complexity"]) {
244
+ lines.push(`max-complexity = ${lint.mccabe["max-complexity"]}`);
245
+ }
246
+ }
247
+ if (lint.pydocstyle) {
248
+ lines.push("[lint.pydocstyle]");
249
+ if (lint.pydocstyle.convention) {
250
+ lines.push(`convention = "${lint.pydocstyle.convention}"`);
251
+ }
252
+ }
253
+ if (lint.isort) {
254
+ lines.push("[lint.isort]");
255
+ if (lint.isort["section-order"]) {
256
+ lines.push(`section-order = ${quoteList(lint.isort["section-order"])}`);
257
+ }
258
+ }
259
+ return lines;
260
+ }
261
+ function toToml(config) {
262
+ const lines = [];
263
+ if (config["line-length"]) {
264
+ lines.push(`line-length = ${config["line-length"]}`);
265
+ }
266
+ if (config.lint) {
267
+ lines.push("[lint]", ...lintSelectionLines(config.lint), ...lintSubsectionLines(config.lint));
268
+ }
269
+ return lines.join("\n") + "\n";
270
+ }
271
+ async function runRuff(config, files, options) {
272
+ const tempDir = mkdtempSync2(join2(tmpdir2(), "codewatch-ruff-"));
273
+ const configPath = join2(tempDir, "ruff.toml");
274
+ try {
275
+ writeFileSync2(configPath, toToml(config), "utf-8");
276
+ const args = [
277
+ "check",
278
+ "--config",
279
+ configPath,
280
+ "--output-format",
281
+ "json",
282
+ ...options?.fix ? ["--fix"] : [],
283
+ ...files
284
+ ];
285
+ const result = await runTool("ruff", args);
286
+ const diagnostics = result.stdout.trim() ? parseRuffJsonOutput(result.stdout) : [];
287
+ return { diagnostics, exitCode: result.exitCode };
288
+ } finally {
289
+ rmSync2(tempDir, { recursive: true, force: true });
290
+ }
291
+ }
292
+
293
+ // src/orchestrator/orchestrate.ts
294
+ function detectLanguage(files) {
295
+ const tsFiles = files.filter((f) => /\.[tj]sx?$/.test(f));
296
+ const pyFiles = files.filter((f) => /\.py$/.test(f));
297
+ if (tsFiles.length > 0 && pyFiles.length === 0) return "typescript";
298
+ if (pyFiles.length > 0 && tsFiles.length === 0) return "python";
299
+ return "mixed";
300
+ }
301
+ function buildSummary(diagnostics) {
302
+ return {
303
+ total: diagnostics.length,
304
+ errors: diagnostics.filter((d) => d.severity === "error").length,
305
+ warnings: diagnostics.filter((d) => d.severity === "warn").length,
306
+ infos: diagnostics.filter((d) => d.severity === "info").length,
307
+ fixed: 0
308
+ };
309
+ }
310
+ async function orchestrate(options) {
311
+ const { profile, files, fix } = options;
312
+ const language = options.language ?? detectLanguage(files);
313
+ const allDiagnostics = [];
314
+ const tsFiles = files.filter((f) => /\.[tj]sx?$/.test(f));
315
+ const pyFiles = files.filter((f) => /\.py$/.test(f));
316
+ if ((language === "typescript" || language === "mixed") && tsFiles.length > 0) {
317
+ const eslintConfig = generateEslintConfig(profile);
318
+ if (eslintConfig.length > 0) {
319
+ const result = await runEslint(eslintConfig, tsFiles, { fix });
320
+ allDiagnostics.push(...result.diagnostics);
321
+ }
322
+ }
323
+ if ((language === "python" || language === "mixed") && pyFiles.length > 0) {
324
+ const ruffConfig = generateRuffConfig(profile);
325
+ if (ruffConfig.lint?.select && ruffConfig.lint.select.length > 0) {
326
+ const result = await runRuff(ruffConfig, pyFiles, { fix });
327
+ allDiagnostics.push(...result.diagnostics);
328
+ }
329
+ }
330
+ return {
331
+ diagnostics: allDiagnostics,
332
+ summary: buildSummary(allDiagnostics)
333
+ };
334
+ }
335
+
336
+ // src/profile-diff/diff-against-profile.ts
337
+ import { PROFILE_CATEGORIES } from "@titan-design/style-profile";
338
+ function getSeverity(confidence, thresholds) {
339
+ if (confidence >= thresholds.error) return "error";
340
+ if (confidence >= thresholds.warn) return "warn";
341
+ return "info";
342
+ }
343
+ function resolveProfileRule(profile, observationType) {
344
+ const [category, rule] = observationType.split(".");
345
+ if (!PROFILE_CATEGORIES.includes(category)) return void 0;
346
+ const section = profile[category];
347
+ if (!section || typeof section !== "object") return void 0;
348
+ const ruleObj = rule === void 0 ? void 0 : section[rule];
349
+ if (!ruleObj || typeof ruleObj !== "object") return void 0;
350
+ if (ruleObj.convention === void 0 || ruleObj.confidence === void 0) {
351
+ return void 0;
352
+ }
353
+ return { convention: ruleObj.convention, confidence: ruleObj.confidence };
354
+ }
355
+ function diffAgainstProfile(profile, observations) {
356
+ const deviations = [];
357
+ let matching = 0;
358
+ for (const obs of observations) {
359
+ const profileRule = resolveProfileRule(profile, obs.type);
360
+ if (!profileRule) continue;
361
+ const expected = String(profileRule.convention);
362
+ const found = String(obs.value);
363
+ if (found === expected) {
364
+ matching++;
365
+ } else {
366
+ deviations.push({
367
+ file: obs.file,
368
+ line: obs.line,
369
+ rule: obs.type,
370
+ expected,
371
+ found,
372
+ severity: getSeverity(
373
+ profileRule.confidence,
374
+ profile.severityThresholds
375
+ )
376
+ });
377
+ }
378
+ }
379
+ return {
380
+ deviations,
381
+ summary: {
382
+ total: observations.length,
383
+ matching,
384
+ deviating: deviations.length
385
+ }
386
+ };
387
+ }
388
+ export {
389
+ diffAgainstProfile,
390
+ formatDiagnostic,
391
+ generateEslintConfig,
392
+ generateRuffConfig,
393
+ orchestrate,
394
+ parseEslintJsonOutput,
395
+ parseRuffJsonOutput
396
+ };
397
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generators/eslint.ts","../src/generators/ruff.ts","../src/runners/eslint-runner.ts","../src/runners/tool-runner.ts","../src/formatters/unified.ts","../src/runners/ruff-runner.ts","../src/orchestrator/orchestrate.ts","../src/profile-diff/diff-against-profile.ts"],"sourcesContent":["import type { Profile } from \"@titan-design/style-profile\";\nimport {\n buildNamingConventionRule,\n buildImportOrderRule,\n buildFunctionLengthRule,\n buildFileNamingRule,\n buildJsdocRules,\n} from \"@titan-design/style-profile\";\n\nexport interface EslintFlatConfigEntry {\n plugins?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n files?: string[];\n}\n\nexport function generateEslintConfig(profile: Profile): EslintFlatConfigEntry[] {\n const entries: EslintFlatConfigEntry[] = [];\n const rules: Record<string, unknown> = {};\n\n const namingRule = buildNamingConventionRule(profile);\n if (namingRule) rules[namingRule[0]] = namingRule[1];\n\n const importRule = buildImportOrderRule(profile);\n if (importRule) rules[importRule[0]] = importRule[1];\n\n const fnLengthRule = buildFunctionLengthRule(profile);\n if (fnLengthRule) rules[fnLengthRule[0]] = fnLengthRule[1];\n\n const fileNamingRule = buildFileNamingRule(profile);\n if (fileNamingRule) rules[fileNamingRule[0]] = fileNamingRule[1];\n\n const jsdocRules = buildJsdocRules(profile);\n for (const [name, value] of jsdocRules) {\n rules[name] = value;\n }\n\n if (Object.keys(rules).length > 0) {\n entries.push({\n files: [\"**/*.ts\", \"**/*.tsx\"],\n rules,\n });\n }\n\n return entries;\n}\n","import type { Profile } from \"@titan-design/style-profile\";\n\nexport interface RuffConfig {\n lint?: {\n select?: string[];\n ignore?: string[];\n fixable?: string[];\n \"per-file-ignores\"?: Record<string, string[]>;\n mccabe?: { \"max-complexity\"?: number };\n pydocstyle?: { convention?: string };\n isort?: {\n \"known-first-party\"?: string[];\n \"section-order\"?: string[];\n };\n };\n \"line-length\"?: number;\n}\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]> & { select: string[] };\n\nfunction addNaming(profile: Profile, lint: RuffLint): void {\n if (profile.naming) {\n const hasNamingRules = Object.values(profile.naming).some(\n (rule) => rule.confidence >= profile.severityThresholds.info,\n );\n if (hasNamingRules) lint.select.push(\"N\");\n }\n}\n\nfunction addImportOrder(profile: Profile, lint: RuffLint): void {\n const importOrder = profile.structure?.importOrder;\n if (importOrder && importOrder.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"I\");\n if (Array.isArray(importOrder.convention)) {\n lint.isort = {\n \"section-order\": importOrder.convention as string[],\n };\n }\n }\n}\n\nfunction addFunctionDocs(profile: Profile, lint: RuffLint): void {\n const functionDocs = profile.documentation?.functionDocs;\n if (functionDocs && functionDocs.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"D\");\n const conv = functionDocs.convention;\n if (\n typeof conv === \"string\" &&\n conv !== \"jsdoc-selective\" &&\n conv !== \"jsdoc-all\"\n ) {\n lint.pydocstyle = { convention: conv };\n }\n }\n}\n\nfunction addComplexity(profile: Profile, lint: RuffLint): void {\n const maxLines = profile.structure?.functionMaxLines;\n if (\n maxLines &&\n maxLines.confidence >= profile.severityThresholds.info &&\n typeof maxLines.convention === \"number\"\n ) {\n lint.select.push(\"C90\");\n lint.mccabe = { \"max-complexity\": maxLines.convention };\n }\n}\n\nexport function generateRuffConfig(profile: Profile): RuffConfig {\n const lint: RuffLint = { select: [] };\n const config: RuffConfig = { lint };\n\n addNaming(profile, lint);\n addImportOrder(profile, lint);\n addFunctionDocs(profile, lint);\n addComplexity(profile, lint);\n\n const lineLength = profile.formatting?.lineLength;\n if (lineLength && typeof lineLength.convention === \"number\") {\n config[\"line-length\"] = lineLength.convention;\n }\n\n return config;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runTool } from \"./tool-runner.js\";\nimport { parseEslintJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic } from \"../orchestrator/types.js\";\nimport type { EslintFlatConfigEntry } from \"../generators/eslint.js\";\n\nexport async function runEslint(\n config: EslintFlatConfigEntry[],\n files: string[],\n options?: { fix?: boolean },\n): Promise<{ diagnostics: CheckDiagnostic[]; exitCode: number }> {\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-eslint-\"));\n const configPath = join(tempDir, \"eslint.config.js\");\n\n try {\n const configContent = `export default ${JSON.stringify(config, null, 2)};`;\n writeFileSync(configPath, configContent, \"utf-8\");\n\n const args = [\n \"--config\",\n configPath,\n \"--format\",\n \"json\",\n ...(options?.fix ? [\"--fix\"] : []),\n ...files,\n ];\n\n const result = await runTool(\"npx\", [\"eslint\", ...args]);\n const diagnostics =\n result.stdout.trim() ? parseEslintJsonOutput(result.stdout) : [];\n\n return { diagnostics, exitCode: result.exitCode };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { spawn } from \"node:child_process\";\n\nexport interface ToolRunResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport function runTool(\n command: string,\n args: string[],\n options?: { cwd?: string; timeout?: number },\n): Promise<ToolRunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n cwd: options?.cwd,\n timeout: options?.timeout ?? 60_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const chunks: Buffer[] = [];\n const errChunks: Buffer[] = [];\n\n child.stdout.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => errChunks.push(chunk));\n\n child.on(\"error\", (err) => {\n reject(new Error(`Failed to spawn ${command}: ${err.message}`));\n });\n\n child.on(\"close\", (code) => {\n resolve({\n stdout: Buffer.concat(chunks).toString(\"utf-8\"),\n stderr: Buffer.concat(errChunks).toString(\"utf-8\"),\n exitCode: code ?? 1,\n });\n });\n });\n}\n","import type { CheckDiagnostic } from \"../orchestrator/types.js\";\n\nconst RUFF_CODE_CATEGORY: Record<string, string> = {\n N: \"naming\",\n I: \"imports\",\n D: \"documentation\",\n C: \"complexity\",\n E: \"formatting\",\n W: \"formatting\",\n F: \"errors\",\n};\n\nconst ESLINT_RULE_CATEGORY: Record<string, string> = {\n \"@typescript-eslint/naming-convention\": \"naming\",\n \"unicorn/filename-case\": \"naming\",\n \"perfectionist/sort-imports\": \"imports\",\n \"import/order\": \"imports\",\n \"max-lines-per-function\": \"structure\",\n \"max-depth\": \"structure\",\n \"jsdoc/require-jsdoc\": \"documentation\",\n};\n\nfunction eslintSeverityToSeverity(eslintSeverity: number): \"error\" | \"warn\" {\n return eslintSeverity === 2 ? \"error\" : \"warn\";\n}\n\nfunction categorizeEslintRule(ruleId: string): string {\n if (ESLINT_RULE_CATEGORY[ruleId]) return ESLINT_RULE_CATEGORY[ruleId];\n if (ruleId.startsWith(\"@typescript-eslint/\")) return \"typescript\";\n if (ruleId.startsWith(\"jsdoc/\")) return \"documentation\";\n if (ruleId.startsWith(\"unicorn/\")) return \"style\";\n if (ruleId.startsWith(\"import/\")) return \"imports\";\n return \"other\";\n}\n\nfunction categorizeRuffCode(code: string): string {\n const prefix = code.replace(/[0-9]/g, \"\");\n return RUFF_CODE_CATEGORY[prefix] ?? \"other\";\n}\n\ninterface EslintJsonEntry {\n filePath: string;\n messages: Array<{\n ruleId: string | null;\n severity: number;\n message: string;\n line: number;\n column: number;\n fix?: { range: [number, number]; text: string } | null;\n }>;\n}\n\ninterface RuffJsonEntry {\n code: string;\n message: string;\n filename: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n fix?: {\n edits: Array<{\n content: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n }>;\n } | null;\n}\n\nexport function parseEslintJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: EslintJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse ESLint JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n const diagnostics: CheckDiagnostic[] = [];\n\n for (const entry of entries) {\n for (const msg of entry.messages) {\n if (!msg.ruleId) continue;\n diagnostics.push({\n file: entry.filePath,\n line: msg.line,\n column: msg.column,\n severity: eslintSeverityToSeverity(msg.severity),\n message: msg.message,\n category: categorizeEslintRule(msg.ruleId),\n rule: msg.ruleId,\n fixable: msg.fix != null,\n fix: msg.fix ?? undefined,\n });\n }\n }\n\n return diagnostics;\n}\n\nexport function parseRuffJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: RuffJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse Ruff JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n return entries.map((entry) => ({\n file: entry.filename,\n line: entry.location.row,\n column: entry.location.column,\n severity: \"warn\" as const,\n message: entry.message,\n category: categorizeRuffCode(entry.code),\n rule: entry.code,\n fixable: entry.fix != null,\n }));\n}\n\nexport function formatDiagnostic(d: CheckDiagnostic): string {\n return `${d.file}:${d.line}:${d.column} ${d.severity} ${d.message} [${d.category}.${d.rule}]`;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runTool } from \"./tool-runner.js\";\nimport { parseRuffJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic } from \"../orchestrator/types.js\";\nimport type { RuffConfig } from \"../generators/ruff.js\";\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]>;\n\nfunction quoteList(values: string[]): string {\n return `[${values.map((s) => `\"${s}\"`).join(\", \")}]`;\n}\n\nfunction lintSelectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.select) lines.push(`select = ${quoteList(lint.select)}`);\n if (lint.ignore) lines.push(`ignore = ${quoteList(lint.ignore)}`);\n return lines;\n}\n\nfunction lintSubsectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.mccabe) {\n lines.push(\"[lint.mccabe]\");\n if (lint.mccabe[\"max-complexity\"]) {\n lines.push(`max-complexity = ${lint.mccabe[\"max-complexity\"]}`);\n }\n }\n if (lint.pydocstyle) {\n lines.push(\"[lint.pydocstyle]\");\n if (lint.pydocstyle.convention) {\n lines.push(`convention = \"${lint.pydocstyle.convention}\"`);\n }\n }\n if (lint.isort) {\n lines.push(\"[lint.isort]\");\n if (lint.isort[\"section-order\"]) {\n lines.push(`section-order = ${quoteList(lint.isort[\"section-order\"])}`);\n }\n }\n return lines;\n}\n\nfunction toToml(config: RuffConfig): string {\n const lines: string[] = [];\n\n if (config[\"line-length\"]) {\n lines.push(`line-length = ${config[\"line-length\"]}`);\n }\n\n if (config.lint) {\n lines.push(\"[lint]\", ...lintSelectionLines(config.lint), ...lintSubsectionLines(config.lint));\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nexport async function runRuff(\n config: RuffConfig,\n files: string[],\n options?: { fix?: boolean },\n): Promise<{ diagnostics: CheckDiagnostic[]; exitCode: number }> {\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-ruff-\"));\n const configPath = join(tempDir, \"ruff.toml\");\n\n try {\n writeFileSync(configPath, toToml(config), \"utf-8\");\n\n const args = [\n \"check\",\n \"--config\",\n configPath,\n \"--output-format\",\n \"json\",\n ...(options?.fix ? [\"--fix\"] : []),\n ...files,\n ];\n\n const result = await runTool(\"ruff\", args);\n const diagnostics =\n result.stdout.trim() ? parseRuffJsonOutput(result.stdout) : [];\n\n return { diagnostics, exitCode: result.exitCode };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { generateEslintConfig } from \"../generators/eslint.js\";\nimport { generateRuffConfig } from \"../generators/ruff.js\";\nimport { runEslint } from \"../runners/eslint-runner.js\";\nimport { runRuff } from \"../runners/ruff-runner.js\";\nimport type {\n OrchestratorOptions,\n OrchestratorResult,\n CheckDiagnostic,\n} from \"./types.js\";\n\nfunction detectLanguage(files: string[]): \"typescript\" | \"python\" | \"mixed\" {\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n if (tsFiles.length > 0 && pyFiles.length === 0) return \"typescript\";\n if (pyFiles.length > 0 && tsFiles.length === 0) return \"python\";\n return \"mixed\";\n}\n\nfunction buildSummary(\n diagnostics: CheckDiagnostic[],\n): OrchestratorResult[\"summary\"] {\n return {\n total: diagnostics.length,\n errors: diagnostics.filter((d) => d.severity === \"error\").length,\n warnings: diagnostics.filter((d) => d.severity === \"warn\").length,\n infos: diagnostics.filter((d) => d.severity === \"info\").length,\n fixed: 0,\n };\n}\n\nexport async function orchestrate(\n options: OrchestratorOptions,\n): Promise<OrchestratorResult> {\n const { profile, files, fix } = options;\n const language = options.language ?? detectLanguage(files);\n const allDiagnostics: CheckDiagnostic[] = [];\n\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n\n if (\n (language === \"typescript\" || language === \"mixed\") &&\n tsFiles.length > 0\n ) {\n const eslintConfig = generateEslintConfig(profile);\n if (eslintConfig.length > 0) {\n const result = await runEslint(eslintConfig, tsFiles, { fix });\n allDiagnostics.push(...result.diagnostics);\n }\n }\n\n if ((language === \"python\" || language === \"mixed\") && pyFiles.length > 0) {\n const ruffConfig = generateRuffConfig(profile);\n if (ruffConfig.lint?.select && ruffConfig.lint.select.length > 0) {\n const result = await runRuff(ruffConfig, pyFiles, { fix });\n allDiagnostics.push(...result.diagnostics);\n }\n }\n\n return {\n diagnostics: allDiagnostics,\n summary: buildSummary(allDiagnostics),\n };\n}\n","import { PROFILE_CATEGORIES } from \"@titan-design/style-profile\";\nimport type { Profile, ProfileCategory } from \"@titan-design/style-profile\";\nimport type { Observation } from \"@titan-design/style-analyzer\";\n\nexport interface Deviation {\n file: string;\n line: number;\n rule: string;\n expected: string;\n found: string;\n severity: \"error\" | \"warn\" | \"info\";\n}\n\nexport interface DiffResult {\n deviations: Deviation[];\n summary: {\n total: number;\n matching: number;\n deviating: number;\n };\n}\n\nfunction getSeverity(\n confidence: number,\n thresholds: Profile[\"severityThresholds\"],\n): \"error\" | \"warn\" | \"info\" {\n if (confidence >= thresholds.error) return \"error\";\n if (confidence >= thresholds.warn) return \"warn\";\n return \"info\";\n}\n\nfunction resolveProfileRule(\n profile: Profile,\n observationType: string,\n): { convention: unknown; confidence: number } | undefined {\n const [category, rule] = observationType.split(\".\");\n if (!PROFILE_CATEGORIES.includes(category as ProfileCategory)) return undefined;\n const section = profile[category as ProfileCategory];\n if (!section || typeof section !== \"object\") return undefined;\n const ruleObj = rule === undefined ? undefined : section[rule];\n if (!ruleObj || typeof ruleObj !== \"object\") return undefined;\n if (ruleObj.convention === undefined || ruleObj.confidence === undefined) {\n return undefined;\n }\n return { convention: ruleObj.convention, confidence: ruleObj.confidence };\n}\n\nexport function diffAgainstProfile(\n profile: Profile,\n observations: Observation[],\n): DiffResult {\n const deviations: Deviation[] = [];\n let matching = 0;\n\n for (const obs of observations) {\n const profileRule = resolveProfileRule(profile, obs.type);\n if (!profileRule) continue;\n\n const expected = String(profileRule.convention);\n const found = String(obs.value);\n\n if (found === expected) {\n matching++;\n } else {\n deviations.push({\n file: obs.file,\n line: obs.line,\n rule: obs.type,\n expected,\n found,\n severity: getSeverity(\n profileRule.confidence,\n profile.severityThresholds,\n ),\n });\n }\n }\n\n return {\n deviations,\n summary: {\n total: observations.length,\n matching,\n deviating: deviations.length,\n },\n };\n}\n"],"mappings":"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","names":["writeFileSync","mkdtempSync","rmSync","join","tmpdir","mkdtempSync","join","tmpdir","writeFileSync","rmSync"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@titan-design/style-checker",
3
+ "version": "0.0.0",
4
+ "description": "Run external lint tools (ruff, ESLint) against configs generated from a style profile, normalize their output into one diagnostic shape, and diff observations against a profile",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "author": "Henry Jewkes",
19
+ "license": "MIT",
20
+ "homepage": "https://github.com/HJewkes/titan-platform#readme",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/HJewkes/titan-platform.git",
24
+ "directory": "packages/style-checker"
25
+ },
26
+ "bugs": "https://github.com/HJewkes/titan-platform/issues",
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@titan-design/style-analyzer": "^0.0.0",
35
+ "@titan-design/style-profile": "^0.0.0"
36
+ },
37
+ "peerDependencies": {
38
+ "zod": "^4.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "zod": "^4.4.3"
42
+ },
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "typecheck": "tsc --noEmit",
46
+ "lint": "eslint src"
47
+ }
48
+ }