@mrclrchtr/supi-tree-sitter 9.1.0 → 9.1.2
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 +3 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +3 -3
- package/src/session/structural-worker-json.ts +23 -0
- package/src/session/structural-worker-node.ts +29 -0
- package/src/session/structural-worker-process.ts +21 -5
- package/src/worker/process-host.mjs +20 -3
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ to access structural code-understanding workflows in pi.
|
|
|
19
19
|
npm install @mrclrchtr/supi-tree-sitter
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Structural analysis always runs in a Node.js child process. Under Bun, including compiled applications such as Pi-Bolt, install Node.js and make the `node` executable available on `PATH`. SuPi validates that executable before it starts the process host. If Node.js is unavailable, structural analysis reports an installation error. There is no Bun Worker or main-thread parser fallback.
|
|
23
|
+
|
|
22
24
|

|
|
23
25
|
|
|
24
26
|
## What you get
|
|
@@ -107,7 +109,7 @@ The benchmark reports cold and repeated outline results. It also reports cold an
|
|
|
107
109
|
|
|
108
110
|
The Structural Worker reads files asynchronously. It identifies fresh parsed files by canonical path, grammar, and SHA-256 content hash. Cached trees use true least-recently-used eviction with limits of 128 files and 32 MiB of retained UTF-8 source bytes. Compiled queries use the same policy with limits of 128 queries and 512 KiB of retained UTF-8 query text. Source and query byte counts are memory-related proxies because `web-tree-sitter` does not report WASM resource sizes. Cached canonical trees stay private in the Worker.
|
|
109
111
|
|
|
110
|
-
The Structural Worker runs in a dedicated Node child process. The process uses `--liftoff-only` so V8 avoids the optimizing WebAssembly compiler that uses unsafe amounts of memory for large grammars. This applies to every grammar in the Worker. Pi's main process does not receive this flag, and a V8 failure in the Worker process cannot crash Pi.
|
|
112
|
+
The Structural Worker runs in a dedicated Node child process. The process uses `--liftoff-only` so V8 avoids the optimizing WebAssembly compiler that uses unsafe amounts of memory for large grammars. This applies to every grammar in the Worker. Pi's main process does not receive this flag, and a V8 failure in the Worker process cannot crash Pi. A Node parent uses its current executable and advanced IPC. A Bun parent selects Node explicitly and uses JSON IPC with bounded base64 result chunks; the private Worker-thread protocol stays unchanged.
|
|
111
113
|
|
|
112
114
|
All structural service operations accept optional `CodeRequestControl`. The parent converts cancellation to one shared atomic flag and forwards absolute deadlines. When present, it forwards only the opaque Debug Operation ID through the Worker protocol so parse and query timing events keep direct request ownership. Direct library calls have no ID. The process host creates a shared atomic flag for its Worker thread because Node IPC cannot share the parent flag. Worker read phases observe a local abort signal. Parser and query progress callbacks observe the flag and deadline. If cooperative interruption does not settle in the fixed 250 ms grace period, the parent terminates the Worker process. Valid queued work keeps FIFO order on one fresh Worker with cold caches. There is no main-thread parser fallback.
|
|
113
115
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.2",
|
|
4
4
|
"description": "Structural AST analysis for SuPi code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"jiti": "^2.7.0",
|
|
38
38
|
"web-tree-sitter": "^0.27.0",
|
|
39
|
-
"@mrclrchtr/supi-code-runtime": "9.1.
|
|
40
|
-
"@mrclrchtr/supi-core": "9.1.
|
|
39
|
+
"@mrclrchtr/supi-code-runtime": "9.1.2",
|
|
40
|
+
"@mrclrchtr/supi-core": "9.1.2"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { assertStructuralProtocolMessageSize } from "./structural-worker-message-size.ts";
|
|
2
|
+
import { STRUCTURAL_WORKER_LIMITS } from "./structural-worker-protocol.ts";
|
|
3
|
+
|
|
4
|
+
/** Restore bounded binary chunks before the private Worker protocol validates them. */
|
|
5
|
+
export function decodeStructuralWorkerJsonMessage(message: unknown): unknown {
|
|
6
|
+
assertStructuralProtocolMessageSize(message);
|
|
7
|
+
if (!message || typeof message !== "object" || !("kind" in message) || message.kind !== "chunk") {
|
|
8
|
+
return message;
|
|
9
|
+
}
|
|
10
|
+
const payload = "payload" in message ? message.payload : undefined;
|
|
11
|
+
const maxLength = Math.ceil(STRUCTURAL_WORKER_LIMITS.targetChunkBytes / 3) * 4;
|
|
12
|
+
if (typeof payload !== "string" || payload.length > maxLength) {
|
|
13
|
+
throw new Error("Invalid Structural Worker JSON chunk payload");
|
|
14
|
+
}
|
|
15
|
+
const bytes = Buffer.from(payload, "base64");
|
|
16
|
+
if (
|
|
17
|
+
bytes.byteLength > STRUCTURAL_WORKER_LIMITS.targetChunkBytes ||
|
|
18
|
+
bytes.toString("base64") !== payload
|
|
19
|
+
) {
|
|
20
|
+
throw new Error("Invalid Structural Worker JSON chunk payload");
|
|
21
|
+
}
|
|
22
|
+
return { ...message, payload: bytes };
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Select a real Node executable without re-entering a compiled Bun application. */
|
|
5
|
+
export function resolveStructuralWorkerNode(): string {
|
|
6
|
+
if (!process.versions.bun) return process.execPath;
|
|
7
|
+
|
|
8
|
+
const probe = spawnSync(
|
|
9
|
+
"node",
|
|
10
|
+
[
|
|
11
|
+
"--liftoff-only",
|
|
12
|
+
"--eval",
|
|
13
|
+
"if (!process.versions.bun) process.stdout.write(process.execPath)",
|
|
14
|
+
],
|
|
15
|
+
{
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
env: { ...process.env, NODE_OPTIONS: "" },
|
|
18
|
+
timeout: 2_000,
|
|
19
|
+
maxBuffer: 4_096,
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
if (probe.status !== 0 || !probe.stdout || !isAbsolute(probe.stdout)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Structural analysis under Bun requires Node.js on PATH. Install Node.js and restart Pi.",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return probe.stdout;
|
|
29
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ChildProcess, fork } from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { decodeStructuralWorkerJsonMessage } from "./structural-worker-json.ts";
|
|
5
|
+
import { resolveStructuralWorkerNode } from "./structural-worker-node.ts";
|
|
4
6
|
import type {
|
|
5
7
|
ParentToStructuralWorkerMessage,
|
|
6
8
|
StructuralWorkerToParentMessage,
|
|
@@ -23,23 +25,28 @@ export function createStructuralWorkerProcess(options: {
|
|
|
23
25
|
readonly generation: number;
|
|
24
26
|
}): StructuralWorkerLike {
|
|
25
27
|
const hostPath = fileURLToPath(new URL("../worker/process-host.mjs", import.meta.url));
|
|
28
|
+
const jsonIpc = Boolean(process.versions.bun);
|
|
29
|
+
const args = [options.cwd, String(options.generation), ...(jsonIpc ? ["json"] : [])];
|
|
26
30
|
// A child process accepts the V8 flag that a Worker thread rejects.
|
|
27
|
-
const child = fork(hostPath,
|
|
31
|
+
const child = fork(hostPath, args, {
|
|
32
|
+
execPath: resolveStructuralWorkerNode(),
|
|
28
33
|
execArgv: ["--liftoff-only"],
|
|
29
34
|
env: { ...process.env, NODE_OPTIONS: "" },
|
|
30
|
-
serialization: "advanced",
|
|
35
|
+
serialization: jsonIpc ? "json" : "advanced",
|
|
31
36
|
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
|
32
37
|
windowsHide: true,
|
|
33
38
|
});
|
|
34
|
-
return new StructuralWorkerProcess(child);
|
|
39
|
+
return new StructuralWorkerProcess(child, jsonIpc);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
class StructuralWorkerProcess implements StructuralWorkerLike {
|
|
38
43
|
readonly #events = new EventEmitter();
|
|
39
44
|
readonly #child: ChildProcess;
|
|
45
|
+
readonly #jsonIpc: boolean;
|
|
40
46
|
|
|
41
|
-
constructor(child: ChildProcess) {
|
|
47
|
+
constructor(child: ChildProcess, jsonIpc: boolean) {
|
|
42
48
|
this.#child = child;
|
|
49
|
+
this.#jsonIpc = jsonIpc;
|
|
43
50
|
child.on("message", (value: unknown) => this.#receive(value));
|
|
44
51
|
child.on("error", (error) => this.#events.emit("error", error));
|
|
45
52
|
child.on("exit", (code) => this.#events.emit("exit", code ?? 1));
|
|
@@ -96,7 +103,16 @@ class StructuralWorkerProcess implements StructuralWorkerLike {
|
|
|
96
103
|
this.#events.emit("error", new Error(value.message.message));
|
|
97
104
|
return;
|
|
98
105
|
}
|
|
99
|
-
|
|
106
|
+
let message: unknown = value.message;
|
|
107
|
+
if (this.#jsonIpc) {
|
|
108
|
+
try {
|
|
109
|
+
message = decodeStructuralWorkerJsonMessage(message);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.#events.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.#events.emit("message", message);
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
2
|
|
|
3
|
-
const [cwd, generationValue] = process.argv.slice(2);
|
|
3
|
+
const [cwd, generationValue, serialization = "advanced"] = process.argv.slice(2);
|
|
4
4
|
const generation = Number(generationValue);
|
|
5
|
-
if (
|
|
5
|
+
if (
|
|
6
|
+
!cwd ||
|
|
7
|
+
!Number.isSafeInteger(generation) ||
|
|
8
|
+
generation < 1 ||
|
|
9
|
+
!["advanced", "json"].includes(serialization)
|
|
10
|
+
) {
|
|
6
11
|
throw new Error("Structural Worker process received invalid startup data");
|
|
7
12
|
}
|
|
8
13
|
|
|
@@ -76,7 +81,19 @@ function isParentChannel(value) {
|
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
function send(message, callback) {
|
|
79
|
-
if (process.connected
|
|
84
|
+
if (!process.connected || !process.send) return;
|
|
85
|
+
// Bun and Node use different binary IPC formats. Keep the thread protocol unchanged.
|
|
86
|
+
const forwarded =
|
|
87
|
+
serialization === "json" && message.message.kind === "chunk"
|
|
88
|
+
? {
|
|
89
|
+
...message,
|
|
90
|
+
message: {
|
|
91
|
+
...message.message,
|
|
92
|
+
payload: Buffer.from(message.message.payload).toString("base64"),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: message;
|
|
96
|
+
process.send(forwarded, callback);
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
function disconnect() {
|