karajan-code 2.33.0 → 3.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.
Files changed (53) hide show
  1. package/README.md +136 -36
  2. package/bin/kj-rag-mcp.js +75 -0
  3. package/docs/README.es.md +4 -4
  4. package/package.json +22 -16
  5. package/packages/hu-board/package.json +5 -2
  6. package/scripts/git-hooks/post-merge +7 -0
  7. package/src/audit/basal-cost.js +17 -12
  8. package/src/audit/deterministic-summary.js +20 -0
  9. package/src/audit/injection-findings.js +105 -0
  10. package/src/checks/hardware.js +150 -0
  11. package/src/checks/node.js +9 -10
  12. package/src/cli/register-meta.js +57 -1
  13. package/src/cli/register-pipeline.js +1 -0
  14. package/src/commands/doctor.js +2 -0
  15. package/src/commands/init.js +41 -1
  16. package/src/commands/rag-mcp.js +18 -0
  17. package/src/commands/rag.js +82 -8
  18. package/src/commands/run.js +8 -0
  19. package/src/commands/telemetry.js +70 -0
  20. package/src/config/defaults.js +11 -1
  21. package/src/lang/chunk-go.js +94 -0
  22. package/src/lang/chunk-java.js +90 -0
  23. package/src/lang/chunk-python.js +88 -0
  24. package/src/lang/chunk-rust.js +102 -0
  25. package/src/lang/go.js +14 -0
  26. package/src/lang/java.js +16 -0
  27. package/src/lang/js.js +8 -0
  28. package/src/lang/python.js +14 -0
  29. package/src/lang/registry.js +79 -0
  30. package/src/lang/rust.js +14 -0
  31. package/src/lang/tree-sitter-loader.js +49 -0
  32. package/src/onboarder/collectors/framework-multilang.js +64 -0
  33. package/src/onboarder/collectors/index.js +18 -1
  34. package/src/orchestrator/stages/coder-stage.js +3 -0
  35. package/src/orchestrator/stages/reviewer-stage.js +11 -0
  36. package/src/rag/auto-update.js +50 -0
  37. package/src/rag/chunker.js +7 -3
  38. package/src/rag/eval.js +61 -0
  39. package/src/rag/indexer.js +81 -10
  40. package/src/rag/retriever.js +57 -5
  41. package/src/rag/vec-store.js +69 -3
  42. package/src/rag/watcher.js +6 -4
  43. package/src/roles/audit-role.js +9 -1
  44. package/src/sonar/scanner.js +51 -16
  45. package/src/utils/manifest-sniff.js +82 -0
  46. package/src/utils/quota-simulator.js +56 -0
  47. package/src/utils/telemetry.js +34 -12
  48. package/templates/roles/onboarder.md +10 -1
  49. package/vendor/tree-sitter-grammars/README.md +21 -0
  50. package/vendor/tree-sitter-grammars/go.wasm +0 -0
  51. package/vendor/tree-sitter-grammars/java.wasm +0 -0
  52. package/vendor/tree-sitter-grammars/python.wasm +0 -0
  53. package/vendor/tree-sitter-grammars/rust.wasm +0 -0
@@ -1,23 +1,25 @@
1
1
  // KJC-TSK-0441 (RAG v2.28.0 PR1) — chokidar watcher. Live re-index of plans
2
2
  // + onboarding + (opt) sources, debounced. PID file arbitrates a single daemon.
3
+ // KJC-TSK-0482 — source matcher derivado del registry (multi-lang: JS/Py/Rust/Go/Java).
3
4
  import { writeFileSync, readFileSync, existsSync, unlinkSync } from "node:fs";
4
- import { join } from "node:path";
5
+ import { extname, join } from "node:path";
5
6
  import chokidar from "chokidar";
6
7
  import { openVecStore, deleteChunksBySource, projectSlug } from "./vec-store.js";
7
8
  import { makeEmbedder } from "./embedders/factory.js";
8
9
  import { indexFile } from "./indexer.js";
9
10
  import { getKarajanHome } from "../utils/paths.js";
11
+ import { getAllCodeExtensions } from "../lang/registry.js";
10
12
 
11
13
  const PIDFILE = () => join(getKarajanHome(), "watcher.pid");
12
14
  const DEFAULT_DEBOUNCE_MS = 1000;
13
15
  const SKIP_SEGMENTS = new Set(["node_modules", ".git", "dist", "build", "coverage", ".karajan", ".next", ".kj", "_diet"]);
14
- const SOURCE_RE = /\.(js|mjs|cjs|ts|tsx|jsx)$/;
16
+ const SOURCE_EXTS = new Set(getAllCodeExtensions().map((e) => e.toLowerCase()));
15
17
 
16
- function isWatchable(path) {
18
+ export function isWatchable(path) {
17
19
  if (path.includes("/.karajan/onboarding/")) return /\.md$/.test(path);
18
20
  if (path.includes("/.karajan/plans/")) return /\.json$/.test(path);
19
21
  if (path.split("/").some((seg) => SKIP_SEGMENTS.has(seg))) return false;
20
- return SOURCE_RE.test(path);
22
+ return SOURCE_EXTS.has(extname(path).toLowerCase());
21
23
  }
22
24
 
23
25
  export function startWatcher({ projectDir, config, logger = console, debounceMs = DEFAULT_DEBOUNCE_MS, withSources = false } = {}) {
@@ -8,6 +8,7 @@ import { collectOsvFindings } from "../audit/osv-findings.js";
8
8
  import { collectSemgrepFindings } from "../audit/semgrep-findings.js";
9
9
  import { collectCircularDeps } from "../audit/circular-deps.js";
10
10
  import { collectDeadExports } from "../audit/dead-exports.js";
11
+ import { collectInjectionFindings } from "../audit/injection-findings.js";
11
12
 
12
13
  function parseDimensions(dimensionsStr) {
13
14
  if (!dimensionsStr || dimensionsStr === "all") return null;
@@ -51,6 +52,7 @@ export class AuditRole extends AgentRole {
51
52
  const noSemgrep = typeof input === "object" ? Boolean(input?.noSemgrep) : false;
52
53
  const noMadge = typeof input === "object" ? Boolean(input?.noMadge) : false;
53
54
  const noKnip = typeof input === "object" ? Boolean(input?.noKnip) : false;
55
+ const noInjectionScan = typeof input === "object" ? Boolean(input?.noInjectionScan) : false;
54
56
  const projectDir = this.config?.projectDir || process.cwd();
55
57
  let basalCost = null;
56
58
  let growthDelta = null;
@@ -61,6 +63,7 @@ export class AuditRole extends AgentRole {
61
63
  let semgrepFindings = null;
62
64
  let circularDeps = null;
63
65
  let deadExports = null;
66
+ let injectionFindings = null;
64
67
  try {
65
68
  basalCost = await measureBasalCost(projectDir);
66
69
  const previous = await loadPreviousAudit(projectDir);
@@ -108,7 +111,12 @@ export class AuditRole extends AgentRole {
108
111
  deadExports = await collectDeadExports(projectDir, stack, this.config, this.logger);
109
112
  } catch { /* knip is best-effort */ }
110
113
  }
111
- return { projectDir, basalCost, growthDelta, stack, sonarFindings, webperf, osvFindings, semgrepFindings, circularDeps, deadExports };
114
+ if (!noInjectionScan) {
115
+ try {
116
+ injectionFindings = await collectInjectionFindings(projectDir, this.logger);
117
+ } catch { /* injection scan is best-effort (KJC-TSK-0498) */ }
118
+ }
119
+ return { projectDir, basalCost, growthDelta, stack, sonarFindings, webperf, osvFindings, semgrepFindings, circularDeps, deadExports, injectionFindings };
112
120
  }
113
121
 
114
122
  /**
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import fsPromises from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import os from "node:os";
4
5
  import { runCommand } from "../utils/process.js";
5
6
  import { sonarUp } from "./manager.js";
6
7
  import { resolveSonarProjectKey } from "./project-key.js";
@@ -10,6 +11,32 @@ import {
10
11
  resolveSonarCredentials,
11
12
  } from "./config-resolver.js";
12
13
 
14
+ export async function findNativeSonarScanner() {
15
+ const which = await runCommand("sh", ["-c", "command -v sonar-scanner"]);
16
+ if (which.exitCode === 0) {
17
+ const bin = String(which.stdout || "").trim();
18
+ if (bin) return bin;
19
+ }
20
+ return null;
21
+ }
22
+
23
+ export async function pickSonarScanner(scannerConfig = {}, deps = {}) {
24
+ const find = deps.findNativeSonarScanner || findNativeSonarScanner;
25
+ const arch = deps.arch || os.arch();
26
+ const forced = String(scannerConfig.command || "").trim().toLowerCase();
27
+ if (forced === "docker") return { type: "docker" };
28
+ if (forced === "native" || forced === "sonar-scanner" || forced.endsWith("/sonar-scanner")) {
29
+ const bin = (forced === "native" || forced === "sonar-scanner") ? await find() : forced;
30
+ if (bin) return { type: "native", bin };
31
+ return { type: "docker", reason: "native sonar-scanner not found on PATH" };
32
+ }
33
+ if (arch === "arm64") {
34
+ const bin = await find();
35
+ if (bin) return { type: "native", bin, reason: "ARM64 host — preferring native binary" };
36
+ }
37
+ return { type: "docker" };
38
+ }
39
+
13
40
  export function buildScannerOpts(projectKey, scanner = {}) {
14
41
  const opts = [`-Dsonar.projectKey=${projectKey}`];
15
42
  if (scanner.sources) opts.push(`-Dsonar.sources=${scanner.sources}`);
@@ -236,31 +263,39 @@ export async function runSonarScan(config, projectKey = null) {
236
263
  ...coverage.scannerPatch
237
264
  });
238
265
 
239
- const args = [
240
- "run",
241
- "--rm",
242
- "-v",
243
- `${process.cwd()}:/usr/src`,
244
- ...(isLocalHost ? ["--add-host", "host.docker.internal:host-gateway"] : []),
245
- ...(isLocalHost || isExternalSonar ? [] : ["--network", sonarNetwork]),
246
- "-e", "SONAR_HOST_URL",
247
- "-e", "SONAR_TOKEN",
248
- "-e", "SONAR_SCANNER_OPTS",
249
- "sonarsource/sonar-scanner-cli"
250
- ];
251
-
252
- // Pass secrets via process env, not CLI args — invisible in `ps aux`
266
+ const pick = await pickSonarScanner(sonarConfig.scanner);
253
267
  const env = {
254
268
  ...process.env,
255
- SONAR_HOST_URL: host,
269
+ SONAR_HOST_URL: pick.type === "native" ? rawHost : host,
256
270
  SONAR_TOKEN: token || "",
257
271
  SONAR_SCANNER_OPTS: buildScannerOpts(effectiveProjectKey, scannerConfig)
258
272
  };
259
273
 
260
- const result = await runCommand("docker", args, { timeout: scannerTimeout, env });
274
+ let cmd, args;
275
+ if (pick.type === "native") {
276
+ cmd = pick.bin;
277
+ args = [];
278
+ } else {
279
+ cmd = "docker";
280
+ args = [
281
+ "run",
282
+ "--rm",
283
+ "-v",
284
+ `${process.cwd()}:/usr/src`,
285
+ ...(isLocalHost ? ["--add-host", "host.docker.internal:host-gateway"] : []),
286
+ ...(isLocalHost || isExternalSonar ? [] : ["--network", sonarNetwork]),
287
+ "-e", "SONAR_HOST_URL",
288
+ "-e", "SONAR_TOKEN",
289
+ "-e", "SONAR_SCANNER_OPTS",
290
+ "sonarsource/sonar-scanner-cli"
291
+ ];
292
+ }
293
+
294
+ const result = await runCommand(cmd, args, { timeout: scannerTimeout, env });
261
295
  return {
262
296
  ok: result.exitCode === 0,
263
297
  projectKey: effectiveProjectKey,
298
+ scanner: pick.type,
264
299
  stdout: result.stdout,
265
300
  stderr: result.stderr,
266
301
  exitCode: result.exitCode
@@ -0,0 +1,82 @@
1
+ // KJC-TSK-0476 — Multi-stack manifest sniffer. Sin libs externas: JSON.parse
2
+ // para package.json, regex para TOML y go.mod. Lo consumen audit (basal-cost)
3
+ // y, vía PR-D, el onboarder.
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+
7
+ async function readMaybe(file) {
8
+ try { return await fs.readFile(file, "utf8"); } catch { return null; }
9
+ }
10
+
11
+ async function sniffNode(dir) {
12
+ const raw = await readMaybe(path.join(dir, "package.json"));
13
+ if (!raw) return null;
14
+ try {
15
+ const pkg = JSON.parse(raw);
16
+ return { stack: "node", manifest: "package.json", name: pkg.name || null,
17
+ deps: Object.keys(pkg.dependencies || {}).length,
18
+ devDeps: Object.keys(pkg.devDependencies || {}).length };
19
+ } catch {
20
+ return { stack: "node", manifest: "package.json", name: null, deps: 0, devDeps: 0, malformed: true };
21
+ }
22
+ }
23
+
24
+ // Cuerpo TOML de `[section]` hasta el siguiente `\n[` o EOF. Lookahead con `/m`
25
+ // cambia el significado de `$` y producía capturas vacías.
26
+ function tomlSection(toml, section) {
27
+ const i = toml.indexOf(`[${section}]`);
28
+ if (i === -1) return "";
29
+ const rest = toml.slice(i + section.length + 2);
30
+ const n = rest.search(/\n\[/);
31
+ return n === -1 ? rest : rest.slice(0, n);
32
+ }
33
+
34
+ const DEP_LINE_RE = /^\s*[A-Za-z0-9_-]+\s*=/;
35
+ const countDepLines = (block, excludePython) => block.split("\n")
36
+ .filter((l) => DEP_LINE_RE.test(l) && !(excludePython && /^\s*python\s*=/.test(l))).length;
37
+
38
+ async function sniffPython(dir) {
39
+ const py = await readMaybe(path.join(dir, "pyproject.toml"));
40
+ if (py) {
41
+ const name = /^name\s*=\s*["']([^"']+)["']/m.exec(py)?.[1] || null;
42
+ const projectDeps = /\[project\][\s\S]*?dependencies\s*=\s*\[([^\]]*)\]/m.exec(py)?.[1] || "";
43
+ const projectCount = projectDeps.split(",").map((s) => s.trim()).filter(Boolean).length;
44
+ const poetryCount = countDepLines(tomlSection(py, "tool.poetry.dependencies"), true);
45
+ return { stack: "python", manifest: "pyproject.toml", name, deps: projectCount + poetryCount, devDeps: 0 };
46
+ }
47
+ const req = await readMaybe(path.join(dir, "requirements.txt"));
48
+ if (!req) return null;
49
+ const lines = req.split("\n").map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean);
50
+ return { stack: "python", manifest: "requirements.txt", name: null, deps: lines.length, devDeps: 0 };
51
+ }
52
+
53
+ async function sniffRust(dir) {
54
+ const toml = await readMaybe(path.join(dir, "Cargo.toml"));
55
+ if (!toml) return null;
56
+ return { stack: "rust", manifest: "Cargo.toml",
57
+ name: /^name\s*=\s*["']([^"']+)["']/m.exec(toml)?.[1] || null,
58
+ deps: countDepLines(tomlSection(toml, "dependencies")),
59
+ devDeps: countDepLines(tomlSection(toml, "dev-dependencies")) };
60
+ }
61
+
62
+ async function sniffGo(dir) {
63
+ const mod = await readMaybe(path.join(dir, "go.mod"));
64
+ if (!mod) return null;
65
+ const block = /require\s*\(([\s\S]*?)\)/m.exec(mod)?.[1] || "";
66
+ const blockCount = block.split("\n").filter((l) => /\S/.test(l) && !l.trim().startsWith("//")).length;
67
+ const singleCount = mod.split("\n").filter((l) => /^require\s+\S+\s+\S+/.test(l)).length;
68
+ return { stack: "go", manifest: "go.mod",
69
+ name: /^module\s+(\S+)/m.exec(mod)?.[1] || null,
70
+ deps: blockCount + singleCount, devDeps: 0 };
71
+ }
72
+
73
+ export async function sniffManifests(dir) {
74
+ const r = await Promise.all([sniffNode(dir), sniffPython(dir), sniffRust(dir), sniffGo(dir)]);
75
+ return r.filter(Boolean);
76
+ }
77
+
78
+ export function summariseStack(manifests) {
79
+ if (!manifests.length) return { primary: null, multi: false, manifests: [] };
80
+ const ranked = [...manifests].sort((a, b) => (b.deps + b.devDeps) - (a.deps + a.devDeps));
81
+ return { primary: ranked[0].stack, multi: manifests.length > 1, manifests };
82
+ }
@@ -0,0 +1,56 @@
1
+ // KJ_SIMULATE_QUOTA — testing hook for the standby flow. See
2
+ // docs/quota-simulation.md. KJC-TSK-0493.
3
+
4
+ let _warned = false;
5
+
6
+ export function parseQuotaSimulationConfig(env = process.env) {
7
+ const trigger = env.KJ_SIMULATE_QUOTA;
8
+ if (!trigger) return null;
9
+ const m = /^after-iter-(\d+)$/i.exec(String(trigger).trim());
10
+ if (!m) {
11
+ if (!_warned) {
12
+ // eslint-disable-next-line no-console
13
+ console.warn(`[KJ_SIMULATE_QUOTA] Ignoring malformed value "${trigger}". Expected "after-iter-<N>".`);
14
+ _warned = true;
15
+ }
16
+ return null;
17
+ }
18
+ return {
19
+ afterIter: Number.parseInt(m[1], 10),
20
+ resetIso: env.KJ_SIMULATE_QUOTA_RESET || new Date(Date.now() + 3600_000).toISOString(),
21
+ agent: String(env.KJ_SIMULATE_QUOTA_AGENT || "claude").toLowerCase(),
22
+ };
23
+ }
24
+
25
+ const SYNTHETIC_MSG = {
26
+ claude: (iso) => `[KJ_SIMULATE_QUOTA] Claude Pro usage limit reached. weekly limit. resets at ${iso}`,
27
+ codex: () => `[KJ_SIMULATE_QUOTA] You have exceeded your current quota. retry after 60 seconds`,
28
+ gemini: () => `[KJ_SIMULATE_QUOTA] Resource exhausted: quota exceeded. retry after 60 seconds`,
29
+ };
30
+
31
+ export function buildSyntheticQuotaMessage(agent, resetIso) {
32
+ return (SYNTHETIC_MSG[agent] || SYNTHETIC_MSG.claude)(resetIso);
33
+ }
34
+
35
+ export function shouldSimulateQuota({ iteration, agent }, env = process.env) {
36
+ const cfg = parseQuotaSimulationConfig(env);
37
+ if (!cfg) return null;
38
+ if (Number(iteration) < cfg.afterIter) return null;
39
+ const a = String(agent || "").toLowerCase();
40
+ if (cfg.agent !== "any" && cfg.agent !== a) return null;
41
+ return { resetIso: cfg.resetIso, agent: a || cfg.agent };
42
+ }
43
+
44
+ // Mutates execResult: forces ok=false and prepends the synthetic quota
45
+ // line to result.error so the existing detectRateLimit branch fires.
46
+ export function applyQuotaSimulation(execResult, { iteration, agent }, env = process.env) {
47
+ const fire = shouldSimulateQuota({ iteration, agent }, env);
48
+ if (!fire || !execResult || typeof execResult !== "object") return false;
49
+ if (!execResult.result || typeof execResult.result !== "object") execResult.result = {};
50
+ execResult.result.error = `${buildSyntheticQuotaMessage(fire.agent, fire.resetIso)}\n${execResult.result.error || ""}`;
51
+ execResult.ok = false;
52
+ return true;
53
+ }
54
+
55
+ // Test seam — reset the one-shot warning between unit tests.
56
+ export function _resetSimulatorWarning() { _warned = false; }
@@ -1,4 +1,30 @@
1
- const TELEMETRY_ENDPOINT = "https://karajan-code.web.app/api/telemetry";
1
+ export const TELEMETRY_ENDPOINT = "https://karajan-code.web.app/api/telemetry";
2
+
3
+ /**
4
+ * Build the exact JSON payload that would be POSTed for an event.
5
+ * Pure function: deterministic given (eventName, data) — exposed so
6
+ * `kj telemetry preview` can render the literal bytes the backend
7
+ * receives without performing any network call.
8
+ *
9
+ * The `data.version` field is folded into `v`; the rest of the
10
+ * event-specific keys ride through `...data` after the common ones,
11
+ * matching the order sent over the wire.
12
+ *
13
+ * @param {string} eventName
14
+ * @param {object} [data]
15
+ * @returns {object}
16
+ */
17
+ export function buildTelemetryPayload(eventName, data = {}) {
18
+ const { version, ...rest } = data;
19
+ return {
20
+ event: eventName,
21
+ v: version || "unknown",
22
+ os: process.platform,
23
+ node: process.version,
24
+ ts: Date.now(),
25
+ ...rest
26
+ };
27
+ }
2
28
 
3
29
  /**
4
30
  * Send an anonymous telemetry event. Non-blocking, fire-and-forget.
@@ -12,14 +38,7 @@ export async function sendTelemetryEvent(eventName, data, config) {
12
38
  if (!isTelemetryEnabled(config)) return;
13
39
 
14
40
  try {
15
- const payload = {
16
- event: eventName,
17
- v: data.version || "unknown",
18
- os: process.platform,
19
- node: process.version,
20
- ts: Date.now(),
21
- ...data
22
- };
41
+ const payload = buildTelemetryPayload(eventName, data);
23
42
  const controller = new AbortController();
24
43
  const timeout = setTimeout(() => controller.abort(), 3000);
25
44
  try {
@@ -48,12 +67,15 @@ export async function sendTelemetryEvent(eventName, data, config) {
48
67
  }
49
68
 
50
69
  /**
51
- * Check if telemetry is enabled (opt-out model).
52
- * Returns false only when the user explicitly sets telemetry: false.
70
+ * Check if telemetry is enabled (opt-in model).
71
+ *
72
+ * Returns true ONLY when the user explicitly answered "yes" in the
73
+ * `kj init` wizard and we persisted `telemetry: true` to their config.
74
+ * Anything else — undefined, null, missing key, false — disables sending.
53
75
  *
54
76
  * @param {object} [config] - Karajan config
55
77
  * @returns {boolean}
56
78
  */
57
79
  export function isTelemetryEnabled(config) {
58
- return config?.telemetry !== false;
80
+ return config?.telemetry === true;
59
81
  }
@@ -14,7 +14,10 @@ If the project is greenfield (no git, no configs, empty tree) emit
14
14
 
15
15
  ## Input
16
16
 
17
- JSON bundle with `projectDir`, `stack`, `tree`, `git` (or null), `configs`,
17
+ JSON bundle with `projectDir`, `stack`, `manifests` (per-stack: node /
18
+ python / rust / go with deps count), `frameworksMultiLang` (Django / Flask /
19
+ FastAPI / Axum / Actix / Rocket / Gin / Echo / Fiber + Rust crate type + Go
20
+ module path + canonical layout dirs), `tree`, `git` (or null), `configs`,
18
21
  `adrs`. Slots may be null/empty.
19
22
 
20
23
  ## Output
@@ -24,6 +27,8 @@ Markdown only — skip any section without data:
24
27
  ```markdown
25
28
  # Architecture Brief — <projectDir>
26
29
  ## Stack
30
+ ## Manifests # only when manifests has entries beyond node
31
+ ## Frameworks # only when frameworksMultiLang has entries
27
32
  ## Layout
28
33
  ## Configs
29
34
  ## Git history
@@ -32,5 +37,9 @@ Markdown only — skip any section without data:
32
37
  ## Recommendations for new work
33
38
  ```
34
39
 
40
+ For non-Node stacks (`frameworksMultiLang[].stack` in {python, rust, go}),
41
+ mention the detected framework(s), crate type or module path, and canonical
42
+ layout dirs (apps/, cmd/, internal/, src/, tests/) when present.
43
+
35
44
  Be conservative: only assert what the bundle shows. "Likely" / "appears to"
36
45
  are fine; invention is not. Aim for ~40–80 lines total.
@@ -0,0 +1,21 @@
1
+ # Vendored tree-sitter grammars
2
+
3
+ WASM binaries used by `src/lang/tree-sitter-loader.js`. Vendored (rather
4
+ than pulled as runtime deps) so the npm package and the SEA binary ship
5
+ exactly the grammars we support — adding all of `@vscode/tree-sitter-wasm`
6
+ would balloon the install by ~22 MB for languages we never chunk.
7
+
8
+ Source: extracted from `@vscode/tree-sitter-wasm@0.3.1`. That package
9
+ re-builds upstream `tree-sitter-*` grammars against the tree-sitter
10
+ 0.25.x ABI, which matches the runtime `web-tree-sitter@0.26.x` we depend
11
+ on. Older prebuilds (e.g. `tree-sitter-wasms@0.1.13`) target an older ABI
12
+ and fail to load.
13
+
14
+ Re-extract with:
15
+
16
+ npm pack @vscode/tree-sitter-wasm
17
+ tar -xzf vscode-tree-sitter-wasm-*.tgz
18
+ cp package/wasm/tree-sitter-python.wasm vendor/tree-sitter-grammars/python.wasm
19
+ cp package/wasm/tree-sitter-rust.wasm vendor/tree-sitter-grammars/rust.wasm
20
+ cp package/wasm/tree-sitter-go.wasm vendor/tree-sitter-grammars/go.wasm
21
+ cp package/wasm/tree-sitter-java.wasm vendor/tree-sitter-grammars/java.wasm