@elaraai/east-claude-plugin 0.0.0-stage → 1.0.90

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,568 @@
1
+ // ../east-diagnostics/dist/src/jsonrpc-stdio.js
2
+ function frame(message) {
3
+ const body = JSON.stringify({ jsonrpc: "2.0", ...message });
4
+ return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
5
+ \r
6
+ ${body}`;
7
+ }
8
+ var FrameReader = class {
9
+ onMessage;
10
+ buffer = Buffer.alloc(0);
11
+ constructor(onMessage) {
12
+ this.onMessage = onMessage;
13
+ }
14
+ push(chunk) {
15
+ this.buffer = Buffer.concat([this.buffer, chunk]);
16
+ for (; ; ) {
17
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
18
+ if (headerEnd < 0)
19
+ return;
20
+ const header = this.buffer.subarray(0, headerEnd).toString("utf8");
21
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
22
+ if (match === null) {
23
+ this.buffer = this.buffer.subarray(headerEnd + 4);
24
+ continue;
25
+ }
26
+ const length = Number(match[1]);
27
+ const bodyStart = headerEnd + 4;
28
+ if (this.buffer.length < bodyStart + length)
29
+ return;
30
+ const body = this.buffer.subarray(bodyStart, bodyStart + length).toString("utf8");
31
+ this.buffer = this.buffer.subarray(bodyStart + length);
32
+ let message;
33
+ try {
34
+ message = JSON.parse(body);
35
+ } catch {
36
+ continue;
37
+ }
38
+ this.onMessage(message);
39
+ }
40
+ }
41
+ reset() {
42
+ this.buffer = Buffer.alloc(0);
43
+ }
44
+ };
45
+
46
+ // ../east-diagnostics/dist/src/python-lint.js
47
+ import { execFile } from "node:child_process";
48
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
49
+ import { tmpdir } from "node:os";
50
+ import { basename, dirname, join } from "node:path";
51
+ var PYTHON_EAST_IMPORT = /^\s*(?:from\s+east(?:\.[\w.]+)?\s+import\b|import\s+east\b)/m;
52
+ function findEastPy(fromDir) {
53
+ const override = process.env["EAST_PY_LINT"];
54
+ if (override !== void 0 && override !== "")
55
+ return override;
56
+ let dir = fromDir;
57
+ for (; ; ) {
58
+ for (const candidate of [join(dir, ".venv", "bin", "east-py"), join(dir, ".venv", "Scripts", "east-py.exe")]) {
59
+ if (existsSync(candidate))
60
+ return candidate;
61
+ }
62
+ const parent = dirname(dir);
63
+ if (parent === dir)
64
+ return "east-py";
65
+ dir = parent;
66
+ }
67
+ }
68
+ var EAST_PY_DEPENDENCY = /elaraai-east-py(?![\w-])|elaraai-east-py-(?:std|io|datascience|cli)(?![\w-])/;
69
+ function findEastPyProject(fromDir) {
70
+ let dir = fromDir;
71
+ for (; ; ) {
72
+ const candidate = join(dir, "pyproject.toml");
73
+ if (existsSync(candidate)) {
74
+ try {
75
+ if (EAST_PY_DEPENDENCY.test(readFileSync(candidate, "utf-8")))
76
+ return dir;
77
+ } catch {
78
+ }
79
+ }
80
+ const parent = dirname(dir);
81
+ if (parent === dir)
82
+ return void 0;
83
+ dir = parent;
84
+ }
85
+ }
86
+ function runEastPyLint(file, content, budgetMs = 4e3, command = findEastPy(dirname(file))) {
87
+ let target = file;
88
+ let scratch = null;
89
+ if (content !== void 0) {
90
+ scratch = mkdtempSync(join(tmpdir(), "east-py-lint-"));
91
+ target = join(scratch, basename(file));
92
+ writeFileSync(target, content, "utf-8");
93
+ }
94
+ return new Promise((resolveFindings) => {
95
+ execFile(
96
+ command,
97
+ ["lint", "--format", "json", target],
98
+ // UTF-8 stdio: python encodes a piped stdout in the locale's code page on Windows (cp1252), and the findings carry em dashes
99
+ { timeout: budgetMs, encoding: "utf-8", maxBuffer: 4 * 1024 * 1024, env: { ...process.env, PYTHONIOENCODING: "utf-8" } },
100
+ (error, stdout) => {
101
+ if (scratch !== null)
102
+ rmSync(scratch, { recursive: true, force: true });
103
+ if (error !== null && error.code !== 1) {
104
+ resolveFindings(null);
105
+ return;
106
+ }
107
+ let records;
108
+ try {
109
+ records = JSON.parse(stdout);
110
+ } catch {
111
+ resolveFindings(null);
112
+ return;
113
+ }
114
+ resolveFindings(Array.isArray(records) ? records : null);
115
+ }
116
+ );
117
+ });
118
+ }
119
+
120
+ // ../east-diagnostics/dist/src/python-lsp-proxy.js
121
+ import { spawn } from "node:child_process";
122
+ import { dirname as dirname2 } from "node:path";
123
+ var INITIALIZE_TIMEOUT_MS = 15e3;
124
+ var RESTART_BACKOFF_MS = 5e3;
125
+ var PythonLspProxy = class {
126
+ options;
127
+ child;
128
+ reader = new FrameReader((message) => this.handle(message));
129
+ nextId = 1;
130
+ ready = false;
131
+ starting;
132
+ lastExitAt = 0;
133
+ lastStderr = "";
134
+ disposed = false;
135
+ /** Documents open on the proxy — what a (re)start replays to the child. */
136
+ open = /* @__PURE__ */ new Map();
137
+ /** path -> the document version the CURRENT child has been sent, by didOpen or didChange. */
138
+ seenByChild = /* @__PURE__ */ new Map();
139
+ /** id -> settle(answered): true when the child replied, false when it went away. */
140
+ pending = /* @__PURE__ */ new Map();
141
+ constructor(options) {
142
+ this.options = options;
143
+ }
144
+ /** Whether a child is up and initialized. */
145
+ get available() {
146
+ return this.ready && this.child !== void 0;
147
+ }
148
+ resolveCommand(fromDir) {
149
+ return (this.options.resolveCommand ?? findEastPy)(fromDir);
150
+ }
151
+ unavailable(reason) {
152
+ this.options.onUnavailable?.(reason);
153
+ }
154
+ /** Start the child if it is not running. Resolves false when it cannot start. */
155
+ async ensure(fromDir) {
156
+ if (this.disposed)
157
+ return false;
158
+ if (this.available)
159
+ return true;
160
+ if (this.starting !== void 0)
161
+ return this.starting;
162
+ if (Date.now() - this.lastExitAt < (this.options.restartBackoffMs ?? RESTART_BACKOFF_MS))
163
+ return false;
164
+ this.starting = this.start(fromDir).finally(() => {
165
+ this.starting = void 0;
166
+ });
167
+ return this.starting;
168
+ }
169
+ async start(fromDir) {
170
+ const command = this.resolveCommand(fromDir);
171
+ let child;
172
+ try {
173
+ const spawnChild = this.options.spawnChild ?? ((c, a) => spawn(c, a, { stdio: "pipe" }));
174
+ child = spawnChild(command, ["lsp"]);
175
+ } catch (error) {
176
+ this.lastExitAt = Date.now();
177
+ this.unavailable(`could not spawn \`${command} lsp\`: ${error.message}`);
178
+ return false;
179
+ }
180
+ this.child = child;
181
+ this.reader.reset();
182
+ this.seenByChild.clear();
183
+ this.lastStderr = "";
184
+ child.on("error", (error) => {
185
+ this.lastStderr = error.message;
186
+ this.handleExit(child);
187
+ });
188
+ child.on("exit", () => {
189
+ const grace = setTimeout(() => this.handleExit(child), 250);
190
+ grace.unref?.();
191
+ });
192
+ child.on("close", () => this.handleExit(child));
193
+ child.stdout.on("data", (chunk) => {
194
+ if (child === this.child)
195
+ this.reader.push(chunk);
196
+ });
197
+ let stderrRest = "";
198
+ child.stderr.on("data", (chunk) => {
199
+ stderrRest += chunk.toString("utf8");
200
+ const lines = stderrRest.split(/\r?\n/);
201
+ stderrRest = lines.pop() ?? "";
202
+ for (const line of lines) {
203
+ if (line.trim() === "")
204
+ continue;
205
+ this.lastStderr = line;
206
+ (this.options.onStderr ?? ((l) => process.stderr.write(`[east-py lsp] ${l}
207
+ `)))(line);
208
+ }
209
+ });
210
+ for (const stream of [child.stdin, child.stdout, child.stderr]) {
211
+ stream.on("error", () => void 0);
212
+ }
213
+ for (const handle of [child, child.stdout, child.stderr, child.stdin]) {
214
+ const unref = handle.unref;
215
+ if (typeof unref === "function")
216
+ unref.call(handle);
217
+ }
218
+ const id = this.nextId++;
219
+ const initialized = new Promise((resolve) => {
220
+ const timer = setTimeout(() => resolve(false), this.options.initializeTimeoutMs ?? INITIALIZE_TIMEOUT_MS);
221
+ this.pending.set(id, (answered) => {
222
+ clearTimeout(timer);
223
+ resolve(answered);
224
+ });
225
+ });
226
+ this.write({ jsonrpc: "2.0", id, method: "initialize", params: { processId: process.pid, rootUri: null, capabilities: {} } });
227
+ const ok = await initialized;
228
+ if (!ok) {
229
+ const alive = child === this.child;
230
+ const why = this.lastStderr !== "" ? this.lastStderr : alive ? `no initialize reply within ${this.options.initializeTimeoutMs ?? INITIALIZE_TIMEOUT_MS} ms` : "exited during the handshake";
231
+ this.handleExit(child);
232
+ this.unavailable(`\`${command} lsp\` did not start: ${why}`);
233
+ return false;
234
+ }
235
+ this.write({ jsonrpc: "2.0", method: "initialized", params: {} });
236
+ this.ready = true;
237
+ for (const [path, doc] of this.open) {
238
+ this.write({ jsonrpc: "2.0", method: "textDocument/didOpen", params: { textDocument: { uri: doc.uri, languageId: "python", version: doc.version, text: doc.text } } });
239
+ this.seenByChild.set(path, doc.version);
240
+ }
241
+ return true;
242
+ }
243
+ handleExit(child) {
244
+ if (child !== this.child)
245
+ return;
246
+ this.child = void 0;
247
+ this.ready = false;
248
+ this.seenByChild.clear();
249
+ this.lastExitAt = Date.now();
250
+ child.removeAllListeners();
251
+ try {
252
+ child.kill();
253
+ } catch {
254
+ }
255
+ for (const stream of [child.stdout, child.stderr, child.stdin]) {
256
+ try {
257
+ stream.destroy();
258
+ } catch {
259
+ }
260
+ }
261
+ for (const settle of this.pending.values())
262
+ settle(false);
263
+ this.pending.clear();
264
+ }
265
+ write(message) {
266
+ const child = this.child;
267
+ if (child === void 0 || child.stdin.destroyed)
268
+ return;
269
+ try {
270
+ child.stdin.write(frame(message));
271
+ } catch {
272
+ this.handleExit(child);
273
+ }
274
+ }
275
+ handle(message) {
276
+ if (message.id !== void 0 && message.id !== null && message.method === void 0) {
277
+ const settle = this.pending.get(message.id);
278
+ if (settle !== void 0) {
279
+ this.pending.delete(message.id);
280
+ settle(true);
281
+ }
282
+ return;
283
+ }
284
+ if (message.method === "textDocument/publishDiagnostics") {
285
+ const uri = message.params?.uri;
286
+ if (typeof uri === "string") {
287
+ this.options.onDiagnostics(uri, message.params?.diagnostics ?? []);
288
+ }
289
+ }
290
+ }
291
+ /**
292
+ * Bring the child up to `doc`: a didOpen when this child has not seen the
293
+ * document, a didChange when it has an older version, nothing when a start
294
+ * just replayed exactly this version.
295
+ */
296
+ deliver(path, doc) {
297
+ const delivered = this.seenByChild.get(path);
298
+ if (delivered === doc.version)
299
+ return;
300
+ if (delivered === void 0) {
301
+ this.write({ jsonrpc: "2.0", method: "textDocument/didOpen", params: { textDocument: { uri: doc.uri, languageId: "python", version: doc.version, text: doc.text } } });
302
+ } else {
303
+ this.write({ jsonrpc: "2.0", method: "textDocument/didChange", params: { textDocument: { uri: doc.uri, version: doc.version }, contentChanges: [{ text: doc.text }] } });
304
+ }
305
+ this.seenByChild.set(path, doc.version);
306
+ }
307
+ /** Forward an opened document, starting the child if needed. */
308
+ async didOpen(path, uri, text) {
309
+ const doc = { uri, text, version: 1 };
310
+ this.open.set(path, doc);
311
+ if (!await this.ensure(dirname2(path)))
312
+ return;
313
+ this.deliver(path, doc);
314
+ }
315
+ /** Forward a change. */
316
+ async didChange(path, uri, text) {
317
+ const known = this.open.get(path);
318
+ const doc = { uri, text, version: (known?.version ?? 0) + 1 };
319
+ this.open.set(path, doc);
320
+ if (!await this.ensure(dirname2(path)))
321
+ return;
322
+ this.deliver(path, doc);
323
+ }
324
+ /** Forward a save — the moment the build tier runs without waiting. */
325
+ async didSave(path, uri, text) {
326
+ const known = this.open.get(path);
327
+ const doc = { uri, text, version: known?.version ?? 1 };
328
+ this.open.set(path, doc);
329
+ if (!await this.ensure(dirname2(path)))
330
+ return;
331
+ this.deliver(path, doc);
332
+ this.write({ jsonrpc: "2.0", method: "textDocument/didSave", params: { textDocument: { uri }, text } });
333
+ }
334
+ /** Forward a close. */
335
+ didClose(path, uri) {
336
+ this.open.delete(path);
337
+ if (!this.available || !this.seenByChild.has(path))
338
+ return;
339
+ this.seenByChild.delete(path);
340
+ this.write({ jsonrpc: "2.0", method: "textDocument/didClose", params: { textDocument: { uri } } });
341
+ }
342
+ /** Stop the child. */
343
+ dispose() {
344
+ this.disposed = true;
345
+ this.open.clear();
346
+ const child = this.child;
347
+ if (child !== void 0)
348
+ this.handleExit(child);
349
+ }
350
+ };
351
+
352
+ // ../east-diagnostics/dist/src/python-lsp-server.js
353
+ import { readFileSync as readFileSync2 } from "node:fs";
354
+ import { dirname as dirname3 } from "node:path";
355
+ import { fileURLToPath } from "node:url";
356
+ var LINT_DEBOUNCE_MS = 100;
357
+ var SEVERITY = { error: 1, warning: 2, suggestion: 3 };
358
+ function uriToPath(uri) {
359
+ if (!uri.startsWith("file://"))
360
+ return void 0;
361
+ try {
362
+ return fileURLToPath(uri);
363
+ } catch {
364
+ return void 0;
365
+ }
366
+ }
367
+ function toDiagnostic(record) {
368
+ const endLine = record.end_line ?? record.line;
369
+ const endColumn = record.end_column ?? record.column + 1;
370
+ return {
371
+ range: {
372
+ start: { line: record.line - 1, character: Math.max(record.column - 1, 0) },
373
+ end: { line: endLine - 1, character: Math.max(endColumn - 1, 0) }
374
+ },
375
+ severity: SEVERITY[record.category] ?? 1,
376
+ code: record.code,
377
+ source: "east-py",
378
+ message: `${record.message} [${record.rule}]`
379
+ };
380
+ }
381
+ function isEastPythonProject(fromDir) {
382
+ const override = process.env["EAST_PY_LINT"];
383
+ if (override !== void 0 && override !== "")
384
+ return true;
385
+ if (findEastPyProject(fromDir) !== void 0)
386
+ return true;
387
+ const command = findEastPy(fromDir);
388
+ return command !== "east-py";
389
+ }
390
+ function runEastPyLsp(options = {}) {
391
+ const input = options.input ?? process.stdin;
392
+ const output = options.output ?? process.stdout;
393
+ const exit = options.exit ?? ((code) => process.exit(code));
394
+ const log = options.log ?? ((line) => process.stderr.write(`[east-py] ${line}
395
+ `));
396
+ const inProject = options.eastPythonProject ?? isEastPythonProject;
397
+ const resolveCommand = options.resolveCommand ?? findEastPy;
398
+ const lint = options.lint ?? ((file, content) => runEastPyLint(file, content, 4e3, resolveCommand(dirname3(file))));
399
+ const open = /* @__PURE__ */ new Map();
400
+ const pendingLint = /* @__PURE__ */ new Map();
401
+ const gated = /* @__PURE__ */ new Map();
402
+ let mode = "child";
403
+ let shuttingDown = false;
404
+ let disposed = false;
405
+ function send(message) {
406
+ output.write(frame(message));
407
+ }
408
+ function publish(uri, diagnostics) {
409
+ send({ method: "textDocument/publishDiagnostics", params: { uri, diagnostics } });
410
+ }
411
+ const python = new PythonLspProxy({
412
+ onDiagnostics: publish,
413
+ onStderr: (line) => log(`east-py lsp: ${line}`),
414
+ onUnavailable: (reason) => {
415
+ if (mode === "lint")
416
+ return;
417
+ mode = "lint";
418
+ log(`${reason} \u2014 falling back to \`east-py lint\` per change; the build tier is off until it starts (east-py lsp --probe says why)`);
419
+ for (const [path, doc] of open)
420
+ lintLater(path, doc.uri);
421
+ },
422
+ ...options.resolveCommand !== void 0 ? { resolveCommand: options.resolveCommand } : {},
423
+ ...options.spawnChild !== void 0 ? { spawnChild: options.spawnChild } : {},
424
+ ...options.initializeTimeoutMs !== void 0 ? { initializeTimeoutMs: options.initializeTimeoutMs } : {},
425
+ ...options.restartBackoffMs !== void 0 ? { restartBackoffMs: options.restartBackoffMs } : {}
426
+ });
427
+ function lintLater(path, uri) {
428
+ const existing = pendingLint.get(path);
429
+ if (existing !== void 0)
430
+ clearTimeout(existing);
431
+ pendingLint.set(path, setTimeout(() => {
432
+ pendingLint.delete(path);
433
+ const doc = open.get(path);
434
+ if (doc === void 0)
435
+ return;
436
+ void lint(path, doc.text).then((records) => {
437
+ if (records === null || open.get(path) !== doc)
438
+ return;
439
+ publish(uri, records.map(toDiagnostic));
440
+ });
441
+ }, LINT_DEBOUNCE_MS));
442
+ }
443
+ function ours(path, text) {
444
+ if (!path.endsWith(".py") || !PYTHON_EAST_IMPORT.test(text))
445
+ return false;
446
+ const dir = dirname3(path);
447
+ let verdict = gated.get(dir);
448
+ if (verdict === void 0) {
449
+ verdict = inProject(dir);
450
+ gated.set(dir, verdict);
451
+ if (!verdict)
452
+ log(`${dir} is not an east-py project (no pyproject.toml above it depends on elaraai-east-py, no .venv east-py, no EAST_PY_LINT): python diagnostics stay idle there`);
453
+ }
454
+ return verdict;
455
+ }
456
+ function forward(kind, path, uri, text) {
457
+ if (!ours(path, text)) {
458
+ const was = open.get(path);
459
+ open.delete(path);
460
+ if (was !== void 0 && mode === "child")
461
+ python.didClose(path, uri);
462
+ publish(uri, []);
463
+ return;
464
+ }
465
+ open.set(path, { uri, text });
466
+ if (mode === "lint") {
467
+ lintLater(path, uri);
468
+ return;
469
+ }
470
+ void (kind === "open" ? python.didOpen(path, uri, text) : kind === "save" ? python.didSave(path, uri, text) : python.didChange(path, uri, text));
471
+ }
472
+ function readSource(path) {
473
+ try {
474
+ return readFileSync2(path, "utf-8");
475
+ } catch {
476
+ return void 0;
477
+ }
478
+ }
479
+ function handle(message) {
480
+ const { method, id, params } = message;
481
+ if (method === void 0)
482
+ return;
483
+ switch (method) {
484
+ case "initialize":
485
+ send({
486
+ id,
487
+ result: {
488
+ capabilities: {
489
+ textDocumentSync: { openClose: true, change: 1, save: { includeText: true } }
490
+ },
491
+ serverInfo: { name: "east-py-diagnostics" }
492
+ }
493
+ });
494
+ return;
495
+ case "initialized":
496
+ return;
497
+ case "shutdown":
498
+ shuttingDown = true;
499
+ send({ id, result: null });
500
+ return;
501
+ case "exit":
502
+ shutdown(shuttingDown ? 0 : 1);
503
+ return;
504
+ case "textDocument/didOpen": {
505
+ const path = uriToPath(params?.textDocument?.uri ?? "");
506
+ const text = params?.textDocument?.text;
507
+ if (path === void 0 || typeof text !== "string")
508
+ return;
509
+ forward("open", path, params.textDocument.uri, text);
510
+ return;
511
+ }
512
+ case "textDocument/didChange": {
513
+ const path = uriToPath(params?.textDocument?.uri ?? "");
514
+ const text = params?.contentChanges?.at?.(-1)?.text;
515
+ if (path === void 0 || typeof text !== "string")
516
+ return;
517
+ forward("change", path, params.textDocument.uri, text);
518
+ return;
519
+ }
520
+ case "textDocument/didSave": {
521
+ const path = uriToPath(params?.textDocument?.uri ?? "");
522
+ if (path === void 0)
523
+ return;
524
+ const text = typeof params?.text === "string" ? params.text : open.get(path)?.text ?? readSource(path);
525
+ if (text === void 0)
526
+ return;
527
+ forward("save", path, params.textDocument.uri, text);
528
+ return;
529
+ }
530
+ case "textDocument/didClose": {
531
+ const uri = params?.textDocument?.uri ?? "";
532
+ const path = uriToPath(uri);
533
+ if (path === void 0)
534
+ return;
535
+ const timer = pendingLint.get(path);
536
+ if (timer !== void 0)
537
+ clearTimeout(timer);
538
+ pendingLint.delete(path);
539
+ if (open.delete(path) && mode === "child")
540
+ python.didClose(path, uri);
541
+ publish(uri, []);
542
+ return;
543
+ }
544
+ default:
545
+ if (id !== void 0) {
546
+ send({ id, error: { code: -32601, message: `Method not found: ${method}` } });
547
+ }
548
+ return;
549
+ }
550
+ }
551
+ const reader = new FrameReader(handle);
552
+ input.on("data", (chunk) => reader.push(chunk));
553
+ const shutdown = (code) => {
554
+ if (disposed)
555
+ return;
556
+ disposed = true;
557
+ for (const timer of pendingLint.values())
558
+ clearTimeout(timer);
559
+ pendingLint.clear();
560
+ python.dispose();
561
+ exit(code);
562
+ };
563
+ input.on("close", () => shutdown(0));
564
+ input.on("end", () => shutdown(0));
565
+ }
566
+
567
+ // ../east-plugin/dist/daemon/east-py-lsp.js
568
+ runEastPyLsp();