ryeos-code 0.1.0__py3-none-any.whl

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,347 @@
1
+ // rye:signed:2026-02-25T00:02:14Z:0270efac388ac0fcc3d6512757396ba070ee053f7db245f39c5f51fbb2c7b823:XF9CIFOfikAnsT4eShnqgXYF1rwHUufdjpcM7EBRhap3OF2BOYLI1ZNKuTfbd0oYKdkonJoOC5YFU5QdGxqpDw==:9fbfabe975fa5a7f
2
+ // rye:unsigned
3
+ import { parseArgs } from "node:util";
4
+ import { execSync } from "node:child_process";
5
+ import { resolve, isAbsolute, relative, extname } from "node:path";
6
+ import { existsSync } from "node:fs";
7
+
8
+ export const __version__ = "1.0.0";
9
+ export const __tool_type__ = "javascript";
10
+ export const __executor_id__ = "rye/core/runtimes/node/node";
11
+ export const __category__ = "rye/code/diagnostics";
12
+ export const __tool_description__ =
13
+ "Run linters and type checkers on source files — ruff, mypy, eslint, tsc, clippy, etc.";
14
+
15
+ export const CONFIG_SCHEMA = {
16
+ type: "object",
17
+ properties: {
18
+ file_path: {
19
+ type: "string",
20
+ description: "Path to file to get diagnostics for",
21
+ },
22
+ linters: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ description:
26
+ "Linters to use (ruff, mypy, pylint for Python; eslint, tsc for JS/TS). Auto-detected if not specified.",
27
+ },
28
+ timeout: {
29
+ type: "integer",
30
+ default: 30,
31
+ description: "Timeout per linter in seconds",
32
+ },
33
+ },
34
+ required: ["file_path"],
35
+ };
36
+
37
+ const MAX_OUTPUT_BYTES = 32768;
38
+ const DEFAULT_TIMEOUT = 30;
39
+
40
+ interface Params {
41
+ file_path: string;
42
+ linters?: string[];
43
+ timeout?: number;
44
+ }
45
+
46
+ interface Diagnostic {
47
+ line: number;
48
+ column: number;
49
+ severity: string;
50
+ message: string;
51
+ code: string;
52
+ }
53
+
54
+ interface Result {
55
+ success: boolean;
56
+ output?: string;
57
+ error?: string;
58
+ diagnostics?: Diagnostic[];
59
+ linters_checked?: string[];
60
+ file_type?: string | null;
61
+ }
62
+
63
+ const FILE_TYPE_MAP: Record<string, string> = {
64
+ ".py": "python",
65
+ ".js": "javascript",
66
+ ".jsx": "javascript",
67
+ ".ts": "typescript",
68
+ ".tsx": "typescript",
69
+ ".mjs": "javascript",
70
+ ".cjs": "javascript",
71
+ ".go": "go",
72
+ ".rs": "rust",
73
+ ".rb": "ruby",
74
+ ".java": "java",
75
+ ".kt": "kotlin",
76
+ ".c": "c",
77
+ ".cpp": "cpp",
78
+ ".h": "c",
79
+ ".hpp": "cpp",
80
+ };
81
+
82
+ const LINTERS_BY_TYPE: Record<string, string[]> = {
83
+ python: ["ruff", "mypy", "pylint", "flake8"],
84
+ javascript: ["eslint", "tsc"],
85
+ typescript: ["eslint", "tsc"],
86
+ go: ["go vet"],
87
+ rust: ["cargo clippy"],
88
+ };
89
+
90
+ function detectFileType(filePath: string): string | null {
91
+ return FILE_TYPE_MAP[extname(filePath).toLowerCase()] ?? null;
92
+ }
93
+
94
+ function isAvailable(cmd: string): boolean {
95
+ try {
96
+ execSync(`which ${cmd.split(" ")[0]}`, {
97
+ encoding: "utf-8",
98
+ stdio: ["pipe", "pipe", "pipe"],
99
+ });
100
+ return true;
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function runLinter(
107
+ linter: string,
108
+ filePath: string,
109
+ cwd: string,
110
+ timeout: number,
111
+ ): Diagnostic[] {
112
+ const diagnostics: Diagnostic[] = [];
113
+
114
+ let cmd: string;
115
+ switch (linter) {
116
+ case "ruff":
117
+ cmd = `ruff check --output-format=json ${filePath}`;
118
+ break;
119
+ case "mypy":
120
+ cmd = `mypy --no-error-summary --no-color-output ${filePath}`;
121
+ break;
122
+ case "pylint":
123
+ cmd = `pylint --output-format=json ${filePath}`;
124
+ break;
125
+ case "flake8":
126
+ cmd = `flake8 --format=default ${filePath}`;
127
+ break;
128
+ case "eslint":
129
+ cmd = `eslint --format=json ${filePath}`;
130
+ break;
131
+ case "tsc":
132
+ cmd = `tsc --noEmit --pretty false ${filePath}`;
133
+ break;
134
+ case "go vet":
135
+ cmd = `go vet ${filePath}`;
136
+ break;
137
+ case "cargo clippy":
138
+ cmd = `cargo clippy --message-format=json ${filePath}`;
139
+ break;
140
+ default:
141
+ return [];
142
+ }
143
+
144
+ let stdout = "";
145
+ let stderr = "";
146
+ try {
147
+ stdout = execSync(cmd, {
148
+ cwd,
149
+ timeout: timeout * 1000,
150
+ encoding: "utf-8",
151
+ stdio: ["pipe", "pipe", "pipe"],
152
+ });
153
+ } catch (e: any) {
154
+ if (e.killed) {
155
+ return [
156
+ {
157
+ line: 0,
158
+ column: 0,
159
+ severity: "error",
160
+ message: `${linter} timed out`,
161
+ code: "timeout",
162
+ },
163
+ ];
164
+ }
165
+ stdout = e.stdout ?? "";
166
+ stderr = e.stderr ?? "";
167
+ }
168
+
169
+ try {
170
+ if (linter === "ruff" && stdout) {
171
+ const issues = JSON.parse(stdout);
172
+ for (const issue of issues) {
173
+ diagnostics.push({
174
+ line: issue.location?.row ?? 0,
175
+ column: issue.location?.column ?? 0,
176
+ severity: issue.severity === "error" ? "error" : "warning",
177
+ message: issue.message ?? "",
178
+ code: issue.code ?? "",
179
+ });
180
+ }
181
+ } else if (linter === "mypy") {
182
+ const pattern = /^(.+?):(\d+): (error|warning|note): (.+)/;
183
+ for (const line of (stdout + stderr).split("\n")) {
184
+ const match = line.match(pattern);
185
+ if (match) {
186
+ diagnostics.push({
187
+ line: parseInt(match[2], 10),
188
+ column: 0,
189
+ severity: match[3] === "note" ? "info" : match[3],
190
+ message: match[4],
191
+ code: "",
192
+ });
193
+ }
194
+ }
195
+ } else if (linter === "pylint" && stdout) {
196
+ const issues = JSON.parse(stdout);
197
+ for (const issue of issues) {
198
+ diagnostics.push({
199
+ line: issue.line ?? 0,
200
+ column: issue.column ?? 0,
201
+ severity: issue.type === "error" ? "error" : "warning",
202
+ message: issue.message ?? "",
203
+ code: issue.symbol ?? "",
204
+ });
205
+ }
206
+ } else if (linter === "flake8") {
207
+ const pattern = /^(.+?):(\d+):(\d+): ([A-Z]\d+) (.+)/;
208
+ for (const line of stdout.split("\n")) {
209
+ const match = line.match(pattern);
210
+ if (match) {
211
+ diagnostics.push({
212
+ line: parseInt(match[2], 10),
213
+ column: parseInt(match[3], 10),
214
+ severity: match[4].startsWith("E") ? "error" : "warning",
215
+ message: match[5],
216
+ code: match[4],
217
+ });
218
+ }
219
+ }
220
+ } else if (linter === "eslint" && stdout) {
221
+ const data = JSON.parse(stdout);
222
+ for (const fileResult of data) {
223
+ for (const msg of fileResult.messages ?? []) {
224
+ diagnostics.push({
225
+ line: msg.line ?? 0,
226
+ column: msg.column ?? 0,
227
+ severity: msg.severity === 2 ? "error" : "warning",
228
+ message: msg.message ?? "",
229
+ code: msg.ruleId ?? "",
230
+ });
231
+ }
232
+ }
233
+ } else if (linter === "tsc") {
234
+ const pattern = /^(.+?)\((\d+),(\d+)\): (error|warning) (TS\d+): (.+)/;
235
+ for (const line of (stdout + stderr).split("\n")) {
236
+ const match = line.match(pattern);
237
+ if (match) {
238
+ diagnostics.push({
239
+ line: parseInt(match[2], 10),
240
+ column: parseInt(match[3], 10),
241
+ severity: match[4],
242
+ message: match[6],
243
+ code: match[5],
244
+ });
245
+ }
246
+ }
247
+ }
248
+ } catch {
249
+ // JSON parse failures are silently ignored (linter produced no valid output)
250
+ }
251
+
252
+ return diagnostics;
253
+ }
254
+
255
+ function formatDiagnostics(diagnostics: Diagnostic[], filePath: string): string {
256
+ if (diagnostics.length === 0) return `No issues found in ${filePath}`;
257
+
258
+ return diagnostics
259
+ .sort((a, b) => a.line - b.line || a.column - b.column)
260
+ .map((d) => {
261
+ const col = d.column ? `:${d.column}` : "";
262
+ const code = d.code ? ` [${d.code}]` : "";
263
+ return `${filePath}:${d.line}${col}: ${d.severity}: ${d.message}${code}`;
264
+ })
265
+ .join("\n");
266
+ }
267
+
268
+ function execute(params: Params, projectPath: string): Result {
269
+ const project = resolve(projectPath);
270
+
271
+ let filePath = params.file_path;
272
+ if (!isAbsolute(filePath)) {
273
+ filePath = resolve(project, filePath);
274
+ }
275
+
276
+ if (!existsSync(filePath)) {
277
+ return { success: false, error: `File not found: ${filePath}` };
278
+ }
279
+
280
+ const fileType = detectFileType(filePath);
281
+ const timeout = params.timeout ?? DEFAULT_TIMEOUT;
282
+
283
+ const candidateLinters = params.linters ?? LINTERS_BY_TYPE[fileType ?? ""] ?? [];
284
+ const availableLinters = candidateLinters.filter((l) =>
285
+ isAvailable(l.split(" ")[0]),
286
+ );
287
+
288
+ if (availableLinters.length === 0) {
289
+ return {
290
+ success: true,
291
+ output: `No linters available for ${fileType ?? "unknown"} files`,
292
+ diagnostics: [],
293
+ linters_checked: [],
294
+ file_type: fileType,
295
+ };
296
+ }
297
+
298
+ const allDiagnostics: Diagnostic[] = [];
299
+ for (const linter of availableLinters) {
300
+ allDiagnostics.push(...runLinter(linter, filePath, project, timeout));
301
+ }
302
+
303
+ // Deduplicate
304
+ const seen = new Set<string>();
305
+ const unique = allDiagnostics.filter((d) => {
306
+ const key = `${d.line}:${d.column}:${d.message}`;
307
+ if (seen.has(key)) return false;
308
+ seen.add(key);
309
+ return true;
310
+ });
311
+
312
+ let relPath: string;
313
+ try {
314
+ relPath = relative(project, filePath);
315
+ } catch {
316
+ relPath = filePath;
317
+ }
318
+
319
+ let output = formatDiagnostics(unique, relPath);
320
+ if (output.length > MAX_OUTPUT_BYTES) {
321
+ output = output.slice(0, MAX_OUTPUT_BYTES) + "\n... [output truncated]";
322
+ }
323
+
324
+ return {
325
+ success: true,
326
+ output,
327
+ diagnostics: unique,
328
+ linters_checked: availableLinters,
329
+ file_type: fileType,
330
+ };
331
+ }
332
+
333
+ // CLI entry point
334
+ const { values } = parseArgs({
335
+ options: {
336
+ params: { type: "string" },
337
+ "project-path": { type: "string" },
338
+ },
339
+ });
340
+
341
+ if (values.params && values["project-path"]) {
342
+ const result = execute(
343
+ JSON.parse(values.params) as Params,
344
+ values["project-path"],
345
+ );
346
+ console.log(JSON.stringify(result));
347
+ }