docbridge 0.11.0 → 0.13.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 (43) hide show
  1. package/CHANGELOG.md +81 -1
  2. package/README.md +16 -9
  3. package/dist/bin/darwin-arm64/SHA256SUMS +3 -3
  4. package/dist/bin/darwin-arm64/docbridge-go-scanner +0 -0
  5. package/dist/bin/darwin-arm64/docbridge-swift-scanner +0 -0
  6. package/dist/bin/darwin-arm64/docbridge_dart_scanner +0 -0
  7. package/dist/bin/linux-x64/SHA256SUMS +2 -2
  8. package/dist/bin/linux-x64/docbridge-go-scanner +0 -0
  9. package/dist/bin/linux-x64/docbridge-swift-scanner +1 -1
  10. package/dist/index.js +1771 -678
  11. package/dist/workers/java/docbridge-java-scanner.jar +0 -0
  12. package/dist/workers/python/docbridge_python_scanner/__init__.py +6 -0
  13. package/dist/workers/python/docbridge_python_scanner/comments.py +177 -0
  14. package/dist/workers/python/docbridge_python_scanner/declarations.py +318 -0
  15. package/dist/workers/python/docbridge_python_scanner/links.py +62 -0
  16. package/dist/workers/python/docbridge_python_scanner/positions.py +75 -0
  17. package/dist/workers/python/docbridge_python_scanner/protocol.py +105 -0
  18. package/dist/workers/python/docbridge_python_scanner/scanner.py +214 -0
  19. package/dist/workers/python/docbridge_python_scanner.py +16 -0
  20. package/dist/workers/ruby/bin/docbridge-ruby-scanner +10 -0
  21. package/dist/workers/ruby/lib/docbridge_ruby_scanner/cli.rb +30 -0
  22. package/dist/workers/ruby/lib/docbridge_ruby_scanner/comments.rb +55 -0
  23. package/dist/workers/ruby/lib/docbridge_ruby_scanner/compat.rb +39 -0
  24. package/dist/workers/ruby/lib/docbridge_ruby_scanner/declarations.rb +230 -0
  25. package/dist/workers/ruby/lib/docbridge_ruby_scanner/links.rb +38 -0
  26. package/dist/workers/ruby/lib/docbridge_ruby_scanner/positions.rb +50 -0
  27. package/dist/workers/ruby/lib/docbridge_ruby_scanner/probe.rb +28 -0
  28. package/dist/workers/ruby/lib/docbridge_ruby_scanner/protocol.rb +161 -0
  29. package/dist/workers/ruby/lib/docbridge_ruby_scanner.rb +21 -0
  30. package/docs/README.md +10 -3
  31. package/docs/user/automation.md +8 -4
  32. package/docs/user/commands.md +11 -3
  33. package/docs/user/configuration.md +111 -17
  34. package/docs/user/getting-started.md +1 -1
  35. package/docs/user/linking.md +106 -10
  36. package/docs/user/troubleshooting.md +26 -9
  37. package/package.json +1 -1
  38. package/schemas/common-output.schema.json +11 -1
  39. package/schemas/context-output.schema.json +11 -1
  40. package/schemas/docbridge.schema.json +118 -2
  41. package/schemas/graph-output.schema.json +11 -1
  42. package/schemas/scanner-worker.schema.json +1 -1
  43. package/templates/skills/docbridge/SKILL.md +12 -2
package/dist/index.js CHANGED
@@ -169132,11 +169132,11 @@ var require_codegen = __commonJS((exports) => {
169132
169132
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
169133
169133
  return `${varKind} ${this.name}${rhs};` + _n;
169134
169134
  }
169135
- optimizeNames(names, constants2) {
169135
+ optimizeNames(names, constants) {
169136
169136
  if (!names[this.name.str])
169137
169137
  return;
169138
169138
  if (this.rhs)
169139
- this.rhs = optimizeExpr(this.rhs, names, constants2);
169139
+ this.rhs = optimizeExpr(this.rhs, names, constants);
169140
169140
  return this;
169141
169141
  }
169142
169142
  get names() {
@@ -169154,10 +169154,10 @@ var require_codegen = __commonJS((exports) => {
169154
169154
  render({ _n }) {
169155
169155
  return `${this.lhs} = ${this.rhs};` + _n;
169156
169156
  }
169157
- optimizeNames(names, constants2) {
169157
+ optimizeNames(names, constants) {
169158
169158
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
169159
169159
  return;
169160
- this.rhs = optimizeExpr(this.rhs, names, constants2);
169160
+ this.rhs = optimizeExpr(this.rhs, names, constants);
169161
169161
  return this;
169162
169162
  }
169163
169163
  get names() {
@@ -169223,8 +169223,8 @@ var require_codegen = __commonJS((exports) => {
169223
169223
  optimizeNodes() {
169224
169224
  return `${this.code}` ? this : undefined;
169225
169225
  }
169226
- optimizeNames(names, constants2) {
169227
- this.code = optimizeExpr(this.code, names, constants2);
169226
+ optimizeNames(names, constants) {
169227
+ this.code = optimizeExpr(this.code, names, constants);
169228
169228
  return this;
169229
169229
  }
169230
169230
  get names() {
@@ -169254,12 +169254,12 @@ var require_codegen = __commonJS((exports) => {
169254
169254
  }
169255
169255
  return nodes.length > 0 ? this : undefined;
169256
169256
  }
169257
- optimizeNames(names, constants2) {
169257
+ optimizeNames(names, constants) {
169258
169258
  const { nodes } = this;
169259
169259
  let i = nodes.length;
169260
169260
  while (i--) {
169261
169261
  const n = nodes[i];
169262
- if (n.optimizeNames(names, constants2))
169262
+ if (n.optimizeNames(names, constants))
169263
169263
  continue;
169264
169264
  subtractNames(names, n.names);
169265
169265
  nodes.splice(i, 1);
@@ -169316,12 +169316,12 @@ var require_codegen = __commonJS((exports) => {
169316
169316
  return;
169317
169317
  return this;
169318
169318
  }
169319
- optimizeNames(names, constants2) {
169319
+ optimizeNames(names, constants) {
169320
169320
  var _a;
169321
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants2);
169322
- if (!(super.optimizeNames(names, constants2) || this.else))
169321
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants);
169322
+ if (!(super.optimizeNames(names, constants) || this.else))
169323
169323
  return;
169324
- this.condition = optimizeExpr(this.condition, names, constants2);
169324
+ this.condition = optimizeExpr(this.condition, names, constants);
169325
169325
  return this;
169326
169326
  }
169327
169327
  get names() {
@@ -169346,10 +169346,10 @@ var require_codegen = __commonJS((exports) => {
169346
169346
  render(opts) {
169347
169347
  return `for(${this.iteration})` + super.render(opts);
169348
169348
  }
169349
- optimizeNames(names, constants2) {
169350
- if (!super.optimizeNames(names, constants2))
169349
+ optimizeNames(names, constants) {
169350
+ if (!super.optimizeNames(names, constants))
169351
169351
  return;
169352
- this.iteration = optimizeExpr(this.iteration, names, constants2);
169352
+ this.iteration = optimizeExpr(this.iteration, names, constants);
169353
169353
  return this;
169354
169354
  }
169355
169355
  get names() {
@@ -169387,10 +169387,10 @@ var require_codegen = __commonJS((exports) => {
169387
169387
  render(opts) {
169388
169388
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
169389
169389
  }
169390
- optimizeNames(names, constants2) {
169391
- if (!super.optimizeNames(names, constants2))
169390
+ optimizeNames(names, constants) {
169391
+ if (!super.optimizeNames(names, constants))
169392
169392
  return;
169393
- this.iterable = optimizeExpr(this.iterable, names, constants2);
169393
+ this.iterable = optimizeExpr(this.iterable, names, constants);
169394
169394
  return this;
169395
169395
  }
169396
169396
  get names() {
@@ -169435,11 +169435,11 @@ var require_codegen = __commonJS((exports) => {
169435
169435
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
169436
169436
  return this;
169437
169437
  }
169438
- optimizeNames(names, constants2) {
169438
+ optimizeNames(names, constants) {
169439
169439
  var _a, _b;
169440
- super.optimizeNames(names, constants2);
169441
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants2);
169442
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
169440
+ super.optimizeNames(names, constants);
169441
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants);
169442
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
169443
169443
  return this;
169444
169444
  }
169445
169445
  get names() {
@@ -169713,7 +169713,7 @@ var require_codegen = __commonJS((exports) => {
169713
169713
  function addExprNames(names, from) {
169714
169714
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
169715
169715
  }
169716
- function optimizeExpr(expr, names, constants2) {
169716
+ function optimizeExpr(expr, names, constants) {
169717
169717
  if (expr instanceof code_1.Name)
169718
169718
  return replaceName(expr);
169719
169719
  if (!canOptimize(expr))
@@ -169728,14 +169728,14 @@ var require_codegen = __commonJS((exports) => {
169728
169728
  return items;
169729
169729
  }, []));
169730
169730
  function replaceName(n) {
169731
- const c = constants2[n.str];
169731
+ const c = constants[n.str];
169732
169732
  if (c === undefined || names[n.str] !== 1)
169733
169733
  return n;
169734
169734
  delete names[n.str];
169735
169735
  return c;
169736
169736
  }
169737
169737
  function canOptimize(e) {
169738
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
169738
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
169739
169739
  }
169740
169740
  }
169741
169741
  function subtractNames(names, from) {
@@ -170197,8 +170197,8 @@ var require_dataType = __commonJS((exports) => {
170197
170197
  return types;
170198
170198
  }
170199
170199
  exports.getSchemaTypes = getSchemaTypes;
170200
- function getJSONTypes(ts2) {
170201
- const types = Array.isArray(ts2) ? ts2 : ts2 ? [ts2] : [];
170200
+ function getJSONTypes(ts3) {
170201
+ const types = Array.isArray(ts3) ? ts3 : ts3 ? [ts3] : [];
170202
170202
  if (types.every(rules_1.isJSONType))
170203
170203
  return types;
170204
170204
  throw new Error("type must be JSONType or JSONType[]: " + types.join(","));
@@ -171223,18 +171223,18 @@ var require_validate = __commonJS((exports) => {
171223
171223
  });
171224
171224
  narrowSchemaTypes(it, types);
171225
171225
  }
171226
- function checkMultipleTypes(it, ts2) {
171227
- if (ts2.length > 1 && !(ts2.length === 2 && ts2.includes("null"))) {
171226
+ function checkMultipleTypes(it, ts3) {
171227
+ if (ts3.length > 1 && !(ts3.length === 2 && ts3.includes("null"))) {
171228
171228
  strictTypesError(it, "use allowUnionTypes to allow union type keyword");
171229
171229
  }
171230
171230
  }
171231
- function checkKeywordTypes(it, ts2) {
171231
+ function checkKeywordTypes(it, ts3) {
171232
171232
  const rules = it.self.RULES.all;
171233
171233
  for (const keyword in rules) {
171234
171234
  const rule = rules[keyword];
171235
171235
  if (typeof rule == "object" && (0, applicability_1.shouldUseRule)(it.schema, rule)) {
171236
171236
  const { type } = rule.definition;
171237
- if (type.length && !type.some((t) => hasApplicableType(ts2, t))) {
171237
+ if (type.length && !type.some((t) => hasApplicableType(ts3, t))) {
171238
171238
  strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`);
171239
171239
  }
171240
171240
  }
@@ -171243,18 +171243,18 @@ var require_validate = __commonJS((exports) => {
171243
171243
  function hasApplicableType(schTs, kwdT) {
171244
171244
  return schTs.includes(kwdT) || kwdT === "number" && schTs.includes("integer");
171245
171245
  }
171246
- function includesType(ts2, t) {
171247
- return ts2.includes(t) || t === "integer" && ts2.includes("number");
171246
+ function includesType(ts3, t) {
171247
+ return ts3.includes(t) || t === "integer" && ts3.includes("number");
171248
171248
  }
171249
171249
  function narrowSchemaTypes(it, withTypes) {
171250
- const ts2 = [];
171250
+ const ts3 = [];
171251
171251
  for (const t of it.dataTypes) {
171252
171252
  if (includesType(withTypes, t))
171253
- ts2.push(t);
171253
+ ts3.push(t);
171254
171254
  else if (withTypes.includes("integer") && t === "number")
171255
- ts2.push("integer");
171255
+ ts3.push("integer");
171256
171256
  }
171257
- it.dataTypes = ts2;
171257
+ it.dataTypes = ts3;
171258
171258
  }
171259
171259
  function strictTypesError(it, msg) {
171260
171260
  const schemaPath = it.schemaEnv.baseId + it.errSchemaPath;
@@ -175576,12 +175576,12 @@ var require_2020 = __commonJS((exports, module) => {
175576
175576
  });
175577
175577
 
175578
175578
  // src/cli/index.ts
175579
- import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
175580
- import { join as join14, resolve as resolve4 } from "node:path";
175579
+ import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync6 } from "node:fs";
175580
+ import { join as join15, resolve as resolve5 } from "node:path";
175581
175581
  // package.json
175582
175582
  var package_default = {
175583
175583
  name: "docbridge",
175584
- version: "0.11.0",
175584
+ version: "0.13.0",
175585
175585
  description: "A CLI that links Markdown specifications and code declarations. Runs on Node.js and Bun.",
175586
175586
  keywords: [
175587
175587
  "bun",
@@ -175716,15 +175716,8 @@ function collectFiles(projectRoot, patterns) {
175716
175716
  if (!stat.isFile()) {
175717
175717
  continue;
175718
175718
  }
175719
- for (const pattern of patterns) {
175720
- if (!matchGlob(pattern, relPath)) {
175721
- continue;
175722
- }
175723
- if (isCodePattern(pattern) && relPath.endsWith(".d.ts")) {
175724
- continue;
175725
- }
175719
+ if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
175726
175720
  matched.add(relPath);
175727
- break;
175728
175721
  }
175729
175722
  }
175730
175723
  };
@@ -175748,9 +175741,6 @@ function readManagedFile(projectRoot, relativePath) {
175748
175741
  };
175749
175742
  }
175750
175743
  }
175751
- function isCodePattern(pattern) {
175752
- return pattern.endsWith(".ts");
175753
- }
175754
175744
  function globToRegExp(pattern) {
175755
175745
  const segments = pattern.split("/");
175756
175746
  const parts = [];
@@ -175788,11 +175778,49 @@ var KNOWN_CODE_LANGUAGES = [
175788
175778
  "swift",
175789
175779
  "dart",
175790
175780
  "rust",
175791
- "go"
175781
+ "go",
175782
+ "javascript",
175783
+ "python",
175784
+ "ruby",
175785
+ "java"
175792
175786
  ];
175793
175787
  function isCodeLanguage(value) {
175794
175788
  return KNOWN_CODE_LANGUAGES.includes(value);
175795
175789
  }
175790
+ var LANGUAGE_SUFFIXES = {
175791
+ typescript: [".ts", ".tsx", ".mts", ".cts"],
175792
+ swift: [".swift"],
175793
+ dart: [".dart"],
175794
+ rust: [".rs"],
175795
+ go: [".go"],
175796
+ javascript: [".js", ".jsx", ".mjs", ".cjs"],
175797
+ python: [".py"],
175798
+ ruby: [".rb"],
175799
+ java: [".java"]
175800
+ };
175801
+ var EXCLUDED_SUFFIXES = {
175802
+ typescript: [".d.ts", ".d.mts", ".d.cts"],
175803
+ swift: [],
175804
+ dart: [],
175805
+ rust: [],
175806
+ go: [],
175807
+ javascript: [],
175808
+ python: [],
175809
+ ruby: [],
175810
+ java: []
175811
+ };
175812
+ function codeFenceLanguage(language, filePath) {
175813
+ if (language === "typescript") {
175814
+ return filePath.endsWith(".tsx") ? "tsx" : "ts";
175815
+ }
175816
+ if (language === "javascript") {
175817
+ return filePath.endsWith(".jsx") ? "jsx" : "js";
175818
+ }
175819
+ return language;
175820
+ }
175821
+ function hasExcludedSuffix(language, relPath) {
175822
+ return EXCLUDED_SUFFIXES[language].some((suffix) => relPath.endsWith(suffix));
175823
+ }
175796
175824
  function collectCodeFiles(projectRoot, codeInclude) {
175797
175825
  const collected = [];
175798
175826
  const seen = new Set;
@@ -175801,7 +175829,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
175801
175829
  if (entry === undefined) {
175802
175830
  continue;
175803
175831
  }
175804
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175832
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175805
175833
  if (seen.has(relPath)) {
175806
175834
  continue;
175807
175835
  }
@@ -175819,7 +175847,7 @@ function codeFileOwners(projectRoot, codeInclude) {
175819
175847
  if (entry === undefined) {
175820
175848
  continue;
175821
175849
  }
175822
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175850
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175823
175851
  const existing = owners.get(relPath);
175824
175852
  if (existing === undefined) {
175825
175853
  owners.set(relPath, [language]);
@@ -175830,28 +175858,77 @@ function codeFileOwners(projectRoot, codeInclude) {
175830
175858
  }
175831
175859
  return owners;
175832
175860
  }
175861
+ function collectLanguageFiles(projectRoot, language, patterns) {
175862
+ return collectFiles(projectRoot, patterns).filter((relPath) => !hasExcludedSuffix(language, relPath));
175863
+ }
175864
+
175865
+ // src/config/scanner-runtimes.ts
175866
+ var RUNTIME_WORKER_LANGUAGES = ["python", "ruby", "java"];
175867
+ var KNOWN_RUNTIME_ENTRY_KEYS = new Set(["command"]);
175868
+ function validateScannerRuntimes(value, diagnostics) {
175869
+ if (!isPlainObject(value)) {
175870
+ diagnostics.push(configDiagnostic("config_invalid_value", "scanners", "`scanners` must be an object keyed by runtime-backed language."));
175871
+ return {};
175872
+ }
175873
+ const result = {};
175874
+ for (const [key, entry] of Object.entries(value)) {
175875
+ if (!isRuntimeWorkerLanguage(key)) {
175876
+ diagnostics.push(configDiagnostic("config_unknown_key", `scanners.${key}`, `Unknown scanner runtime: ${key}. Supported runtimes: ${RUNTIME_WORKER_LANGUAGES.join(", ")}.`));
175877
+ continue;
175878
+ }
175879
+ const command = validateRuntimeEntry(key, entry, diagnostics);
175880
+ if (command !== undefined) {
175881
+ result[key] = { command };
175882
+ }
175883
+ }
175884
+ return result;
175885
+ }
175886
+ function validateRuntimeEntry(language, value, diagnostics) {
175887
+ const target = `scanners.${language}`;
175888
+ if (!isPlainObject(value)) {
175889
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`command\` array.`));
175890
+ return;
175891
+ }
175892
+ for (const key of Object.keys(value)) {
175893
+ if (!KNOWN_RUNTIME_ENTRY_KEYS.has(key)) {
175894
+ diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
175895
+ }
175896
+ }
175897
+ const command = value.command;
175898
+ if (!Array.isArray(command) || command.length === 0 || !command.every((part) => typeof part === "string" && part !== "")) {
175899
+ diagnostics.push(configDiagnostic("config_invalid_value", `${target}.command`, `\`${target}.command\` must be a non-empty array of non-empty strings: the runtime executable followed by its arguments.`));
175900
+ return;
175901
+ }
175902
+ return command;
175903
+ }
175904
+ function isRuntimeWorkerLanguage(value) {
175905
+ return RUNTIME_WORKER_LANGUAGES.includes(value);
175906
+ }
175907
+ function configDiagnostic(code, target, message) {
175908
+ return { severity: "error", code, target, message };
175909
+ }
175910
+ function isPlainObject(value) {
175911
+ return typeof value === "object" && value !== null && !Array.isArray(value);
175912
+ }
175833
175913
 
175834
175914
  // src/config/config.ts
175835
175915
  var CONFIG_FILE_NAME = "docbridge.config.json";
175836
175916
  var EMPTY_CONFIG = {
175837
175917
  include: { code: {}, docs: [] }
175838
175918
  };
175839
- var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
175919
+ var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
175840
175920
  var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
175841
175921
  var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
175842
- var LANGUAGE_SUFFIX = {
175843
- typescript: ".ts",
175844
- swift: ".swift",
175845
- dart: ".dart",
175846
- rust: ".rs",
175847
- go: ".go"
175848
- };
175849
175922
  var LANGUAGE_VISIBILITY = {
175850
175923
  typescript: ["public", "protected", "private"],
175851
175924
  swift: ["public", "open", "internal"],
175852
175925
  dart: ["public"],
175853
175926
  rust: ["pub", "private"],
175854
- go: ["exported", "unexported"]
175927
+ go: ["exported", "unexported"],
175928
+ javascript: ["public", "protected", "private"],
175929
+ python: ["public", "private"],
175930
+ ruby: ["public", "protected", "private"],
175931
+ java: ["public", "protected", "package", "private"]
175855
175932
  };
175856
175933
  function loadConfig(projectRoot) {
175857
175934
  let rawText;
@@ -175880,7 +175957,7 @@ function resolveConfig(rawText) {
175880
175957
  config: EMPTY_CONFIG,
175881
175958
  ok: false,
175882
175959
  diagnostics: [
175883
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
175960
+ configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
175884
175961
  ]
175885
175962
  };
175886
175963
  }
@@ -175893,57 +175970,61 @@ function resolveConfig(rawText) {
175893
175970
  config: EMPTY_CONFIG,
175894
175971
  ok: false,
175895
175972
  diagnostics: [
175896
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
175973
+ configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
175897
175974
  ]
175898
175975
  };
175899
175976
  }
175900
175977
  const diagnostics = [];
175901
- if (!isPlainObject(parsed)) {
175902
- diagnostics.push(configDiagnostic("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
175978
+ if (!isPlainObject2(parsed)) {
175979
+ diagnostics.push(configDiagnostic2("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
175903
175980
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175904
175981
  }
175905
175982
  for (const key of Object.keys(parsed)) {
175906
175983
  if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
175907
- diagnostics.push(configDiagnostic("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
175984
+ diagnostics.push(configDiagnostic2("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
175908
175985
  }
175909
175986
  }
175910
175987
  if ("$schema" in parsed && typeof parsed.$schema !== "string") {
175911
- diagnostics.push(configDiagnostic("config_invalid_value", "$schema", "`$schema` must be a string."));
175988
+ diagnostics.push(configDiagnostic2("config_invalid_value", "$schema", "`$schema` must be a string."));
175912
175989
  }
175990
+ const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
175913
175991
  const include = parsed.include;
175914
- if (!isPlainObject(include)) {
175915
- diagnostics.push(configDiagnostic("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175992
+ if (!isPlainObject2(include)) {
175993
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175916
175994
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175917
175995
  }
175918
175996
  for (const key of Object.keys(include)) {
175919
175997
  if (!KNOWN_INCLUDE_KEYS.has(key)) {
175920
- diagnostics.push(configDiagnostic("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
175998
+ diagnostics.push(configDiagnostic2("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
175921
175999
  }
175922
176000
  }
175923
176001
  const code = validateCodeInclude(include.code, diagnostics);
175924
- validatePatternArray(include.docs, "docs", ".md", false, diagnostics);
176002
+ validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
175925
176003
  const ok = diagnostics.length === 0;
175926
- const config = ok ? { include: { code, docs: include.docs } } : EMPTY_CONFIG;
176004
+ const config = ok ? {
176005
+ include: { code, docs: include.docs },
176006
+ ...scanners === undefined ? {} : { scanners }
176007
+ } : EMPTY_CONFIG;
175927
176008
  return { config, diagnostics, ok };
175928
176009
  }
175929
176010
  function validateCodeInclude(value, diagnostics) {
175930
176011
  if (Array.isArray(value)) {
175931
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
176012
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
175932
176013
  return {};
175933
176014
  }
175934
- if (!isPlainObject(value)) {
175935
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
176015
+ if (!isPlainObject2(value)) {
176016
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
175936
176017
  return {};
175937
176018
  }
175938
176019
  const keys = Object.keys(value);
175939
176020
  if (keys.length === 0) {
175940
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
176021
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
175941
176022
  return {};
175942
176023
  }
175943
176024
  const result = {};
175944
176025
  for (const key of keys) {
175945
176026
  if (!isCodeLanguage(key)) {
175946
- diagnostics.push(configDiagnostic("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
176027
+ diagnostics.push(configDiagnostic2("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
175947
176028
  continue;
175948
176029
  }
175949
176030
  const entry = validateCodeEntry(key, value[key], diagnostics);
@@ -175955,20 +176036,20 @@ function validateCodeInclude(value, diagnostics) {
175955
176036
  }
175956
176037
  function validateCodeEntry(language, value, diagnostics) {
175957
176038
  const target = `include.code.${language}`;
175958
- if (!isPlainObject(value)) {
175959
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
176039
+ if (!isPlainObject2(value)) {
176040
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
175960
176041
  return;
175961
176042
  }
175962
176043
  for (const key of Object.keys(value)) {
175963
176044
  if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
175964
- diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
176045
+ diagnostics.push(configDiagnostic2("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
175965
176046
  }
175966
176047
  }
175967
176048
  const before = diagnostics.length;
175968
- validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIX[language], language === "typescript", diagnostics);
176049
+ validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIXES[language], EXCLUDED_SUFFIXES[language], diagnostics);
175969
176050
  if ("visibility" in value) {
175970
176051
  if (!isStringArray(value.visibility)) {
175971
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
176052
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
175972
176053
  } else {
175973
176054
  validateVisibilityOptions(language, value.visibility, target, diagnostics);
175974
176055
  }
@@ -175985,47 +176066,49 @@ function validateCodeEntry(language, value, diagnostics) {
175985
176066
  function validateVisibilityOptions(language, values, target, diagnostics) {
175986
176067
  const allowed = LANGUAGE_VISIBILITY[language];
175987
176068
  if (allowed.length === 0) {
175988
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
176069
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
175989
176070
  return;
175990
176071
  }
175991
176072
  for (const value of values) {
175992
176073
  if (!allowed.includes(value)) {
175993
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
176074
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
175994
176075
  }
175995
176076
  }
175996
176077
  }
175997
- function validatePatternArray(value, field, requiredSuffix, excludeDeclarationFiles, diagnostics) {
176078
+ function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
175998
176079
  const target = `include.${field}`;
175999
176080
  if (!Array.isArray(value)) {
176000
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
176081
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
176001
176082
  return;
176002
176083
  }
176003
176084
  if (value.length === 0) {
176004
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
176085
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
176005
176086
  return;
176006
176087
  }
176007
176088
  for (const pattern of value) {
176008
176089
  if (typeof pattern !== "string") {
176009
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
176090
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
176010
176091
  continue;
176011
176092
  }
176012
176093
  const globResult = validateGlobPattern(pattern);
176013
176094
  if (!globResult.ok) {
176014
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
176095
+ diagnostics.push(configDiagnostic2("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
176015
176096
  continue;
176016
176097
  }
176017
- const suffixError = checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles);
176098
+ const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
176018
176099
  if (suffixError !== undefined) {
176019
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, suffixError));
176100
+ diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
176020
176101
  }
176021
176102
  }
176022
176103
  }
176023
- function checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles) {
176024
- if (!pattern.endsWith(requiredSuffix)) {
176025
- return `Pattern must end with \`${requiredSuffix}\`.`;
176104
+ function checkPatternSuffix(pattern, suffixes, excludedSuffixes) {
176105
+ if (!suffixes.some((suffix) => pattern.endsWith(suffix))) {
176106
+ const listed = suffixes.map((suffix) => `\`${suffix}\``).join(", ");
176107
+ return suffixes.length === 1 ? `Pattern must end with ${listed}.` : `Pattern must end with one of ${listed}.`;
176026
176108
  }
176027
- if (excludeDeclarationFiles && pattern.endsWith(".d.ts")) {
176028
- return "Pattern must not target `.d.ts` declaration files.";
176109
+ const excluded = excludedSuffixes.find((suffix) => pattern.endsWith(suffix));
176110
+ if (excluded !== undefined) {
176111
+ return `Pattern must not target \`${excluded}\` declaration files.`;
176029
176112
  }
176030
176113
  return;
176031
176114
  }
@@ -176033,15 +176116,15 @@ function detectLanguageOverlap(projectRoot, code) {
176033
176116
  const diagnostics = [];
176034
176117
  for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
176035
176118
  if (owners.length > 1) {
176036
- diagnostics.push(configDiagnostic("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
176119
+ diagnostics.push(configDiagnostic2("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
176037
176120
  }
176038
176121
  }
176039
176122
  return diagnostics;
176040
176123
  }
176041
- function configDiagnostic(code, target, message) {
176124
+ function configDiagnostic2(code, target, message) {
176042
176125
  return { severity: "error", code, target, message };
176043
176126
  }
176044
- function isPlainObject(value) {
176127
+ function isPlainObject2(value) {
176045
176128
  return typeof value === "object" && value !== null && !Array.isArray(value);
176046
176129
  }
176047
176130
  function isStringArray(value) {
@@ -176568,6 +176651,11 @@ function positionOf(range) {
176568
176651
  // src/lsp/server.ts
176569
176652
  import { fileURLToPath as fileURLToPath3 } from "node:url";
176570
176653
 
176654
+ // src/shared/error.ts
176655
+ function reasonOf(error) {
176656
+ return error instanceof Error ? error.message : String(error);
176657
+ }
176658
+
176571
176659
  // src/lsp/position.ts
176572
176660
  function toLspPosition(position) {
176573
176661
  return { line: position.line - 1, character: position.column - 1 };
@@ -176951,32 +177039,27 @@ function renderCodeSignatures(state, counterparts) {
176951
177039
  const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
176952
177040
  const fenced = signature.length > 0 ? `
176953
177041
 
176954
- \`\`\`${FENCE_LANGUAGE[symbol.language]}
177042
+ \`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
176955
177043
  ${signature}
176956
177044
  \`\`\`` : "";
176957
177045
  blocks.push(`**${symbol.endpoint}**${fenced}`);
176958
177046
  }
176959
177047
  return blocks.length > 0 ? blocks.join(DIVIDER) : null;
176960
177048
  }
176961
- var FENCE_LANGUAGE = {
176962
- typescript: "ts",
176963
- swift: "swift",
176964
- dart: "dart",
176965
- rust: "rust",
176966
- go: "go"
176967
- };
176968
177049
  var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
177050
+ var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
176969
177051
  function withoutLeadingComments(text, language) {
177052
+ const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
176970
177053
  let index = 0;
176971
177054
  for (;; ) {
176972
177055
  while (index < text.length && /\s/.test(text[index] ?? "")) {
176973
177056
  index += 1;
176974
177057
  }
176975
- if (text.startsWith("//", index)) {
177058
+ if (text.startsWith(lineComment, index)) {
176976
177059
  const newline = text.indexOf(`
176977
177060
  `, index);
176978
177061
  index = newline === -1 ? text.length : newline + 1;
176979
- } else if (text.startsWith("/*", index)) {
177062
+ } else if (lineComment === "//" && text.startsWith("/*", index)) {
176980
177063
  index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
176981
177064
  } else {
176982
177065
  return text.slice(index);
@@ -177517,24 +177600,205 @@ function manifestLink(source, target, entry, side) {
177517
177600
  };
177518
177601
  }
177519
177602
 
177603
+ // src/shared/cancelable.ts
177604
+ function deferred() {
177605
+ let settle;
177606
+ const promise = new Promise((resolve2, reject) => {
177607
+ settle = { resolve: resolve2, reject };
177608
+ });
177609
+ return {
177610
+ promise,
177611
+ resolve: (value) => settle?.resolve(value),
177612
+ reject: (error) => settle?.reject(error)
177613
+ };
177614
+ }
177615
+ function settledCancelable(value) {
177616
+ return { promise: Promise.resolve(value), cancel: () => {
177617
+ return;
177618
+ } };
177619
+ }
177620
+ function mapCancelable(task, map) {
177621
+ const mapped = deferred();
177622
+ let cancelled = false;
177623
+ task.promise.then((value) => {
177624
+ if (cancelled) {
177625
+ throw abortError();
177626
+ }
177627
+ return map(value);
177628
+ }).then(mapped.resolve, mapped.reject);
177629
+ return {
177630
+ promise: mapped.promise,
177631
+ cancel() {
177632
+ cancelled = true;
177633
+ mapped.reject(abortError());
177634
+ task.cancel();
177635
+ }
177636
+ };
177637
+ }
177638
+ function abortError() {
177639
+ const error = new Error("The operation was cancelled");
177640
+ error.name = "AbortError";
177641
+ return error;
177642
+ }
177643
+ function isAbortError(error) {
177644
+ return error instanceof Error && error.name === "AbortError";
177645
+ }
177646
+ function cancelableSequence(body) {
177647
+ const outcome = deferred();
177648
+ let cancelled = false;
177649
+ let running;
177650
+ const step = async (next) => {
177651
+ if (cancelled) {
177652
+ if (typeof next !== "function") {
177653
+ next.promise.catch(() => {
177654
+ return;
177655
+ });
177656
+ next.cancel();
177657
+ }
177658
+ throw abortError();
177659
+ }
177660
+ const task = typeof next === "function" ? next() : next;
177661
+ running = task;
177662
+ try {
177663
+ return await task.promise;
177664
+ } finally {
177665
+ if (running === task) {
177666
+ running = undefined;
177667
+ }
177668
+ }
177669
+ };
177670
+ (async () => body(step))().then(outcome.resolve, outcome.reject);
177671
+ return {
177672
+ promise: outcome.promise,
177673
+ cancel() {
177674
+ if (cancelled) {
177675
+ return;
177676
+ }
177677
+ cancelled = true;
177678
+ outcome.reject(abortError());
177679
+ running?.cancel();
177680
+ }
177681
+ };
177682
+ }
177683
+
177684
+ // src/scan/code/scan-cache.ts
177685
+ import { createHash } from "node:crypto";
177686
+ function emptyCodeScanCache() {
177687
+ return { fingerprint: "", entries: new Map };
177688
+ }
177689
+ function codeScanCacheKey(language, filePath, content, options, argv, runtime = []) {
177690
+ const contentHash = createHash("sha256").update(content).digest("hex");
177691
+ return JSON.stringify([
177692
+ language,
177693
+ filePath,
177694
+ contentHash,
177695
+ options.visibility ?? null,
177696
+ argv,
177697
+ runtime
177698
+ ]);
177699
+ }
177700
+ function isReusableScan(scan) {
177701
+ return !scan.diagnostics.some((diagnostic) => diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed");
177702
+ }
177703
+
177520
177704
  // src/scan/code/typescript.ts
177705
+ var import_typescript2 = __toESM(require_typescript(), 1);
177706
+
177707
+ // src/scan/code/typescript-diagnostics.ts
177521
177708
  var import_typescript = __toESM(require_typescript(), 1);
177522
- var LANGUAGE = "typescript";
177709
+ function scriptLanguage(filePath) {
177710
+ return LANGUAGE_SUFFIXES.javascript.some((suffix) => filePath.endsWith(suffix)) ? "javascript" : "typescript";
177711
+ }
177712
+ var LABEL = {
177713
+ typescript: "TypeScript",
177714
+ javascript: "JavaScript"
177715
+ };
177716
+ var SUPPORTED_DECLARATIONS = {
177717
+ typescript: "Supported declarations are top-level exported function, class, interface, type, single-declarator const, enum, and named default function or class, plus the identifier-named members of a class, interface, or object type alias.",
177718
+ javascript: "Supported declarations are top-level ESM-exported function, class, single-declarator const, let, or var, and named default function or class, plus the identifier-named members of a class. CommonJS assignments are not declarations."
177719
+ };
177720
+ function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
177721
+ const location = { filePath, line: 1, column: 1 };
177722
+ if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
177723
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
177724
+ location.line = line + 1;
177725
+ location.column = character + 1;
177726
+ }
177727
+ const language = scriptLanguage(filePath);
177728
+ const detail = diagnostic === undefined ? `${LABEL[language]} file has a syntactic parse error.` : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
177729
+ `);
177730
+ return {
177731
+ severity: "error",
177732
+ code: "code_parse_error",
177733
+ language,
177734
+ target: filePath,
177735
+ message: `${LABEL[language]} parse error: ${detail}`,
177736
+ location
177737
+ };
177738
+ }
177739
+ function unsupportedDeclarationDiagnostic(filePath, location) {
177740
+ const language = scriptLanguage(filePath);
177741
+ return {
177742
+ severity: "warning",
177743
+ code: "unsupported_declaration",
177744
+ language,
177745
+ target: filePath,
177746
+ message: `@doc is attached to an unsupported declaration. ${SUPPORTED_DECLARATIONS[language]}`,
177747
+ location
177748
+ };
177749
+ }
177750
+ function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
177751
+ const diagnostic = {
177752
+ severity: "error",
177753
+ code: "duplicate_code_symbol",
177754
+ language: scriptLanguage(location.filePath),
177755
+ target: endpoint,
177756
+ message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
177757
+ location
177758
+ };
177759
+ if (range !== undefined) {
177760
+ diagnostic.range = range;
177761
+ }
177762
+ return diagnostic;
177763
+ }
177764
+ function duplicateLinkDiagnostic(source, target, location, range) {
177765
+ const diagnostic = {
177766
+ severity: "warning",
177767
+ code: "duplicate_link",
177768
+ target,
177769
+ source,
177770
+ message: `Duplicate @doc link from ${source} to ${target}.`,
177771
+ location
177772
+ };
177773
+ if (range !== undefined) {
177774
+ diagnostic.range = range;
177775
+ }
177776
+ return diagnostic;
177777
+ }
177778
+
177779
+ // src/scan/code/typescript.ts
177523
177780
  var typeScriptAdapter = {
177524
- language: LANGUAGE,
177781
+ language: "typescript",
177525
177782
  scanFile(filePath, content, options) {
177526
177783
  return scanTypeScript(filePath, content, options);
177784
+ },
177785
+ scanFiles(files, options) {
177786
+ return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
177527
177787
  }
177528
177788
  };
177789
+ var javaScriptAdapter = {
177790
+ ...typeScriptAdapter,
177791
+ language: "javascript"
177792
+ };
177529
177793
  var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
177530
177794
  function scanTypeScript(filePath, content, options = {}) {
177531
177795
  const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
177532
- const sourceFile = import_typescript.default.createSourceFile(filePath, content, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
177533
- const parseDiagnostics = getParseDiagnostics(sourceFile);
177534
- if (parseDiagnostics.length > 0) {
177535
- const first = parseDiagnostics[0];
177796
+ const sourceFile = import_typescript2.default.createSourceFile(filePath, content, import_typescript2.default.ScriptTarget.Latest, true, scriptKindOf(filePath));
177797
+ const syntaxErrors = syntaxDiagnostics(sourceFile);
177798
+ if (syntaxErrors.length > 0) {
177799
+ const first = syntaxErrors[0];
177536
177800
  return {
177537
- language: LANGUAGE,
177801
+ language: scriptLanguage(filePath),
177538
177802
  filePath,
177539
177803
  symbols: [],
177540
177804
  undocumentedSymbols: [],
@@ -177604,11 +177868,13 @@ function scanTypeScript(filePath, content, options = {}) {
177604
177868
  }
177605
177869
  const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
177606
177870
  if (!parsed.ok) {
177607
- diagnostics.push(parsed.diagnostic);
177871
+ diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
177608
177872
  continue;
177609
177873
  }
177610
177874
  if (linkTargetsSeen.has(docTag.rawTarget)) {
177611
- diagnostics.push(duplicateLinkDiagnostic(endpoint, docTag.rawTarget, docTag.location, docTag.targetRange));
177875
+ const { rawTarget, location, targetRange } = docTag;
177876
+ const duplicate = duplicateLinkDiagnostic(endpoint, rawTarget, location, targetRange);
177877
+ diagnostics.push(linkDiagnostic(filePath, duplicate));
177612
177878
  continue;
177613
177879
  }
177614
177880
  linkTargetsSeen.add(docTag.rawTarget);
@@ -177624,7 +177890,7 @@ function scanTypeScript(filePath, content, options = {}) {
177624
177890
  }
177625
177891
  }
177626
177892
  return {
177627
- language: LANGUAGE,
177893
+ language: scriptLanguage(filePath),
177628
177894
  filePath,
177629
177895
  symbols,
177630
177896
  undocumentedSymbols,
@@ -177632,15 +177898,60 @@ function scanTypeScript(filePath, content, options = {}) {
177632
177898
  diagnostics
177633
177899
  };
177634
177900
  }
177901
+ function linkDiagnostic(filePath, diagnostic) {
177902
+ if (scriptLanguage(filePath) !== "javascript") {
177903
+ return diagnostic;
177904
+ }
177905
+ const { severity, code, ...rest } = diagnostic;
177906
+ return { severity, code, language: "javascript", ...rest };
177907
+ }
177908
+ function scriptKindOf(filePath) {
177909
+ if (scriptLanguage(filePath) === "javascript") {
177910
+ return filePath.endsWith(".jsx") ? import_typescript2.default.ScriptKind.JSX : import_typescript2.default.ScriptKind.JS;
177911
+ }
177912
+ return filePath.endsWith(".tsx") ? import_typescript2.default.ScriptKind.TSX : import_typescript2.default.ScriptKind.TS;
177913
+ }
177914
+ function syntaxDiagnostics(sourceFile) {
177915
+ const parseDiagnostics = getParseDiagnostics(sourceFile);
177916
+ if (parseDiagnostics.length > 0 || scriptLanguage(sourceFile.fileName) !== "javascript") {
177917
+ return parseDiagnostics;
177918
+ }
177919
+ return javaScriptGrammarDiagnostics(sourceFile);
177920
+ }
177635
177921
  function getParseDiagnostics(sourceFile) {
177636
177922
  const withDiagnostics = sourceFile;
177637
177923
  return withDiagnostics.parseDiagnostics ?? [];
177638
177924
  }
177925
+ function javaScriptGrammarDiagnostics(sourceFile) {
177926
+ const { fileName } = sourceFile;
177927
+ const host = {
177928
+ getSourceFile: (name) => name === fileName ? sourceFile : undefined,
177929
+ fileExists: (name) => name === fileName,
177930
+ readFile: () => {
177931
+ return;
177932
+ },
177933
+ writeFile: () => {
177934
+ return;
177935
+ },
177936
+ getDefaultLibFileName: () => "lib.d.ts",
177937
+ getCurrentDirectory: () => "/",
177938
+ getCanonicalFileName: (name) => name,
177939
+ useCaseSensitiveFileNames: () => true,
177940
+ getNewLine: () => `
177941
+ `
177942
+ };
177943
+ const program = import_typescript2.default.createProgram({
177944
+ rootNames: [fileName],
177945
+ options: { allowJs: true, noLib: true, noResolve: true, types: [] },
177946
+ host
177947
+ });
177948
+ return program.getSyntacticDiagnostics(sourceFile);
177949
+ }
177639
177950
  function collectDocTags(filePath, sourceFile, statement) {
177640
177951
  const node = jsDocCarrier(statement);
177641
177952
  const location = locationOf(filePath, sourceFile, statement);
177642
177953
  const tags = [];
177643
- for (const tag of import_typescript.default.getJSDocTags(node)) {
177954
+ for (const tag of import_typescript2.default.getJSDocTags(node)) {
177644
177955
  if (tag.tagName.escapedText !== "doc") {
177645
177956
  continue;
177646
177957
  }
@@ -177659,7 +177970,7 @@ function collectDocTags(filePath, sourceFile, statement) {
177659
177970
  return tags;
177660
177971
  }
177661
177972
  function jsDocCarrier(statement) {
177662
- if (import_typescript.default.isVariableStatement(statement)) {
177973
+ if (import_typescript2.default.isVariableStatement(statement)) {
177663
177974
  const declaration = statement.declarationList.declarations[0];
177664
177975
  if (declaration !== undefined) {
177665
177976
  return declaration;
@@ -177705,11 +178016,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
177705
178016
  }
177706
178017
  }
177707
178018
  function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
177708
- if (!import_typescript.default.isConstructorDeclaration(member)) {
178019
+ if (!import_typescript2.default.isConstructorDeclaration(member)) {
177709
178020
  return;
177710
178021
  }
177711
178022
  for (const parameter of member.parameters) {
177712
- if (!import_typescript.default.isParameterPropertyDeclaration(parameter, member)) {
178023
+ if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
177713
178024
  continue;
177714
178025
  }
177715
178026
  if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
@@ -177725,22 +178036,22 @@ function describeContainer(statement) {
177725
178036
  return { name: supportedNameNode(statement)?.text ?? null, members };
177726
178037
  }
177727
178038
  function containerMembers(statement) {
177728
- if (import_typescript.default.isClassDeclaration(statement)) {
178039
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177729
178040
  return statement.members;
177730
178041
  }
177731
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178042
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177732
178043
  return statement.members;
177733
178044
  }
177734
- if (import_typescript.default.isEnumDeclaration(statement)) {
178045
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177735
178046
  return statement.members;
177736
178047
  }
177737
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
177738
- return import_typescript.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
178048
+ if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
178049
+ return import_typescript2.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
177739
178050
  }
177740
- if (import_typescript.default.isVariableStatement(statement)) {
178051
+ if (import_typescript2.default.isVariableStatement(statement)) {
177741
178052
  const declarations = statement.declarationList.declarations;
177742
178053
  const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
177743
- return initializer !== undefined && import_typescript.default.isClassExpression(initializer) ? initializer.members : null;
178054
+ return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
177744
178055
  }
177745
178056
  return null;
177746
178057
  }
@@ -177762,24 +178073,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
177762
178073
  };
177763
178074
  }
177764
178075
  function isVisibleMember(member, visibility) {
177765
- if (hasModifier(member, import_typescript.default.SyntaxKind.PrivateKeyword)) {
178076
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
177766
178077
  return visibility.has("private");
177767
178078
  }
177768
- if (hasModifier(member, import_typescript.default.SyntaxKind.ProtectedKeyword)) {
178079
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
177769
178080
  return visibility.has("protected");
177770
178081
  }
177771
178082
  return visibility.has("public");
177772
178083
  }
177773
178084
  function memberIdentity(sourceFile, member) {
177774
- if (import_typescript.default.isConstructorDeclaration(member)) {
177775
- const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript.default.SyntaxKind.ConstructorKeyword);
178085
+ if (import_typescript2.default.isConstructorDeclaration(member)) {
178086
+ const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript2.default.SyntaxKind.ConstructorKeyword);
177776
178087
  return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
177777
178088
  }
177778
- if (!import_typescript.default.isMethodDeclaration(member) && !import_typescript.default.isPropertyDeclaration(member) && !import_typescript.default.isGetAccessorDeclaration(member) && !import_typescript.default.isSetAccessorDeclaration(member) && !import_typescript.default.isMethodSignature(member) && !import_typescript.default.isPropertySignature(member)) {
178089
+ if (!import_typescript2.default.isMethodDeclaration(member) && !import_typescript2.default.isPropertyDeclaration(member) && !import_typescript2.default.isGetAccessorDeclaration(member) && !import_typescript2.default.isSetAccessorDeclaration(member) && !import_typescript2.default.isMethodSignature(member) && !import_typescript2.default.isPropertySignature(member)) {
177779
178090
  return null;
177780
178091
  }
177781
178092
  const name = member.name;
177782
- return import_typescript.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
178093
+ return import_typescript2.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
177783
178094
  }
177784
178095
  function memberSignatureEndOffset(sourceFile, member) {
177785
178096
  const body = member.body;
@@ -177788,28 +178099,28 @@ function memberSignatureEndOffset(sourceFile, member) {
177788
178099
  function supportedNameNode(statement) {
177789
178100
  const isExported = hasExportModifier(statement);
177790
178101
  const isDefault = hasDefaultModifier(statement);
177791
- if (import_typescript.default.isFunctionDeclaration(statement)) {
178102
+ if (import_typescript2.default.isFunctionDeclaration(statement)) {
177792
178103
  if (!isExported || statement.name === undefined) {
177793
178104
  return null;
177794
178105
  }
177795
178106
  return statement.name;
177796
178107
  }
177797
- if (import_typescript.default.isClassDeclaration(statement)) {
178108
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177798
178109
  if (!isExported || statement.name === undefined) {
177799
178110
  return null;
177800
178111
  }
177801
178112
  return statement.name;
177802
178113
  }
177803
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178114
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177804
178115
  return isExported && !isDefault ? statement.name : null;
177805
178116
  }
177806
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
178117
+ if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
177807
178118
  return isExported && !isDefault ? statement.name : null;
177808
178119
  }
177809
- if (import_typescript.default.isEnumDeclaration(statement)) {
178120
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177810
178121
  return isExported && !isDefault ? statement.name : null;
177811
178122
  }
177812
- if (import_typescript.default.isVariableStatement(statement)) {
178123
+ if (import_typescript2.default.isVariableStatement(statement)) {
177813
178124
  if (!isExported) {
177814
178125
  return null;
177815
178126
  }
@@ -177819,23 +178130,23 @@ function supportedNameNode(statement) {
177819
178130
  return null;
177820
178131
  }
177821
178132
  const name = declaration.name;
177822
- return import_typescript.default.isIdentifier(name) ? name : null;
178133
+ return import_typescript2.default.isIdentifier(name) ? name : null;
177823
178134
  }
177824
178135
  return null;
177825
178136
  }
177826
178137
  function hasExportModifier(statement) {
177827
- return hasModifier(statement, import_typescript.default.SyntaxKind.ExportKeyword);
178138
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
177828
178139
  }
177829
178140
  function hasDefaultModifier(statement) {
177830
- return hasModifier(statement, import_typescript.default.SyntaxKind.DefaultKeyword);
178141
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
177831
178142
  }
177832
178143
  function hasModifier(node, kind) {
177833
- const modifiers = import_typescript.default.canHaveModifiers(node) ? import_typescript.default.getModifiers(node) : undefined;
178144
+ const modifiers = import_typescript2.default.canHaveModifiers(node) ? import_typescript2.default.getModifiers(node) : undefined;
177834
178145
  return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
177835
178146
  }
177836
178147
  function declarationLocation(filePath, sourceFile, node) {
177837
- const carrier = import_typescript.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
177838
- const name = carrier === undefined ? undefined : import_typescript.default.getNameOfDeclaration(carrier);
178148
+ const carrier = import_typescript2.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
178149
+ const name = carrier === undefined ? undefined : import_typescript2.default.getNameOfDeclaration(carrier);
177839
178150
  return locationOf(filePath, sourceFile, name ?? node);
177840
178151
  }
177841
178152
  function locationOf(filePath, sourceFile, node) {
@@ -177854,13 +178165,13 @@ function rangeOfNode(sourceFile, node) {
177854
178165
  return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
177855
178166
  }
177856
178167
  function signatureEndOffset(sourceFile, statement) {
177857
- if (import_typescript.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
178168
+ if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
177858
178169
  return statement.body.getStart(sourceFile);
177859
178170
  }
177860
- if (import_typescript.default.isClassDeclaration(statement)) {
178171
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177861
178172
  return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
177862
178173
  }
177863
- if (import_typescript.default.isVariableStatement(statement)) {
178174
+ if (import_typescript2.default.isVariableStatement(statement)) {
177864
178175
  return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
177865
178176
  }
177866
178177
  return statement.getEnd();
@@ -177876,13 +178187,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
177876
178187
  if (initializer === undefined) {
177877
178188
  return;
177878
178189
  }
177879
- if ((import_typescript.default.isArrowFunction(initializer) || import_typescript.default.isFunctionExpression(initializer)) && import_typescript.default.isBlock(initializer.body)) {
178190
+ if ((import_typescript2.default.isArrowFunction(initializer) || import_typescript2.default.isFunctionExpression(initializer)) && import_typescript2.default.isBlock(initializer.body)) {
177880
178191
  return initializer.body.getStart(sourceFile);
177881
178192
  }
177882
- if (import_typescript.default.isObjectLiteralExpression(initializer)) {
178193
+ if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
177883
178194
  return initializer.getStart(sourceFile);
177884
178195
  }
177885
- if (import_typescript.default.isClassExpression(initializer)) {
178196
+ if (import_typescript2.default.isClassExpression(initializer)) {
177886
178197
  return classBodyStartOffset(sourceFile, initializer);
177887
178198
  }
177888
178199
  return;
@@ -177897,7 +178208,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
177897
178208
  function makeCodeSymbol(filePath, endpoint, declaration) {
177898
178209
  const symbol = {
177899
178210
  kind: "code",
177900
- language: LANGUAGE,
178211
+ language: scriptLanguage(filePath),
177901
178212
  filePath,
177902
178213
  symbolName: declaration.symbolName,
177903
178214
  canonicalId: declaration.canonicalId,
@@ -177934,224 +178245,20 @@ function firstToken(text) {
177934
178245
  }
177935
178246
  return trimmed.split(/\s+/)[0];
177936
178247
  }
177937
- function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
177938
- const location = { filePath, line: 1, column: 1 };
177939
- if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
177940
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
177941
- location.line = line + 1;
177942
- location.column = character + 1;
177943
- }
177944
- const detail = diagnostic === undefined ? "TypeScript file has a syntactic parse error." : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
177945
- `);
177946
- return {
177947
- severity: "error",
177948
- code: "code_parse_error",
177949
- language: LANGUAGE,
177950
- target: filePath,
177951
- message: `TypeScript parse error: ${detail}`,
177952
- location
177953
- };
177954
- }
177955
- function unsupportedDeclarationDiagnostic(filePath, location) {
177956
- return {
177957
- severity: "warning",
177958
- code: "unsupported_declaration",
177959
- language: LANGUAGE,
177960
- target: filePath,
177961
- message: "@doc is attached to an unsupported declaration. Supported declarations are top-level exported function, class, interface, type, single-declarator const, enum, and named default function or class, plus the identifier-named members of a class, interface, or object type alias.",
177962
- location
177963
- };
177964
- }
177965
- function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
177966
- const diagnostic = {
177967
- severity: "error",
177968
- code: "duplicate_code_symbol",
177969
- language: LANGUAGE,
177970
- target: endpoint,
177971
- message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
177972
- location
177973
- };
177974
- if (range !== undefined) {
177975
- diagnostic.range = range;
177976
- }
177977
- return diagnostic;
177978
- }
177979
- function duplicateLinkDiagnostic(source, target, location, range) {
177980
- const diagnostic = {
177981
- severity: "warning",
177982
- code: "duplicate_link",
177983
- target,
177984
- source,
177985
- message: `Duplicate @doc link from ${source} to ${target}.`,
177986
- location
177987
- };
177988
- if (range !== undefined) {
177989
- diagnostic.range = range;
177990
- }
177991
- return diagnostic;
177992
- }
177993
178248
 
177994
- // src/scan/code/worker/scanner-executable.ts
177995
- import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
177996
- import { join as join5 } from "node:path";
178249
+ // src/scan/code/worker/runtime-worker.ts
178250
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
178251
+ import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
177997
178252
 
177998
- // src/shared/error.ts
177999
- function reasonOf(error) {
178000
- return error instanceof Error ? error.message : String(error);
178001
- }
178253
+ // src/scan/code/worker/runtime-probe.ts
178254
+ import { spawnSync as spawnSync2 } from "node:child_process";
178002
178255
 
178003
- // src/shared/package-root.ts
178004
- import { existsSync, realpathSync } from "node:fs";
178256
+ // src/scan/code/worker/scanner-worker.ts
178257
+ var import__2020 = __toESM(require_2020(), 1);
178258
+ import { spawn, spawnSync } from "node:child_process";
178259
+ import { mkdirSync } from "node:fs";
178260
+ import { tmpdir } from "node:os";
178005
178261
  import { dirname, join as join4 } from "node:path";
178006
- import { fileURLToPath as fileURLToPath2 } from "node:url";
178007
- function resolvePackageRoot(moduleUrl = import.meta.url) {
178008
- const modulePath = fileURLToPath2(moduleUrl);
178009
- let resolved;
178010
- try {
178011
- resolved = realpathSync(modulePath);
178012
- } catch {
178013
- resolved = modulePath;
178014
- }
178015
- let dir = dirname(resolved);
178016
- for (;; ) {
178017
- if (existsSync(join4(dir, "templates", "skills"))) {
178018
- return dir;
178019
- }
178020
- const parent = dirname(dir);
178021
- if (parent === dir) {
178022
- return dir;
178023
- }
178024
- dir = parent;
178025
- }
178026
- }
178027
-
178028
- // src/scan/code/worker/scanner-executable.ts
178029
- var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
178030
- var SCANNER_EXECUTABLE_NAMES = {
178031
- swift: "docbridge-swift-scanner",
178032
- dart: "docbridge_dart_scanner",
178033
- rust: "docbridge-rust-scanner",
178034
- go: "docbridge-go-scanner"
178035
- };
178036
- function scannerPlatformKey() {
178037
- return `${process.platform}-${process.arch}`;
178038
- }
178039
- function scannerExecutableName(language) {
178040
- return SCANNER_EXECUTABLE_NAMES[language];
178041
- }
178042
- function resolveScannerWorkerCommand(language, options = {}) {
178043
- const platformKey = options.platformKey ?? scannerPlatformKey();
178044
- const platformSupported = isSupportedScannerPlatformKey(platformKey);
178045
- const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
178046
- const found = candidates.find((candidate) => existsSync2(candidate));
178047
- if (found !== undefined) {
178048
- const repair = ensureExecutable(found, options.chmod ?? chmodSync);
178049
- if (!repair.ok) {
178050
- return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
178051
- }
178052
- return { ok: true, command: [found] };
178053
- }
178054
- if (!platformSupported) {
178055
- return {
178056
- ok: false,
178057
- diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178058
- };
178059
- }
178060
- return {
178061
- ok: false,
178062
- diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178063
- };
178064
- }
178065
- function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
178066
- const sourceRoot = options.sourceRoot ?? sourceRootPath();
178067
- const distRoot = options.distRoot ?? distRootPath();
178068
- const executable = scannerExecutableName(language);
178069
- if (language === "swift") {
178070
- return [
178071
- join5(sourceRoot, "packages/swift-scanner/.build/release", executable),
178072
- join5(sourceRoot, "packages/swift-scanner/.build/debug", executable),
178073
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178074
- ];
178075
- }
178076
- if (language === "rust") {
178077
- return [
178078
- join5(sourceRoot, "packages/rust-scanner/target/release", executable),
178079
- join5(sourceRoot, "packages/rust-scanner/target/debug", executable),
178080
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178081
- ];
178082
- }
178083
- if (language === "go") {
178084
- return [
178085
- join5(sourceRoot, "packages/go-scanner/bin", executable),
178086
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178087
- ];
178088
- }
178089
- return [
178090
- join5(sourceRoot, "packages/dart-scanner/bin", executable),
178091
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178092
- ];
178093
- }
178094
- function ensureExecutable(path, chmod) {
178095
- let mode;
178096
- try {
178097
- mode = statSync(path).mode;
178098
- } catch (error) {
178099
- return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
178100
- }
178101
- if (isExecutableByThisProcess(path)) {
178102
- return { ok: true };
178103
- }
178104
- try {
178105
- chmod(path, mode | 73);
178106
- } catch (error) {
178107
- return {
178108
- ok: false,
178109
- reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
178110
- };
178111
- }
178112
- return { ok: true };
178113
- }
178114
- function isExecutableByThisProcess(path) {
178115
- try {
178116
- accessSync(path, constants.X_OK);
178117
- return true;
178118
- } catch {
178119
- return false;
178120
- }
178121
- }
178122
- function formatMode(mode) {
178123
- return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
178124
- }
178125
- function isSupportedScannerPlatformKey(platformKey) {
178126
- return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
178127
- }
178128
- function scannerRootsFromModuleUrl(moduleUrl) {
178129
- const packageRoot = resolvePackageRoot(moduleUrl);
178130
- return { distRoot: join5(packageRoot, "dist"), sourceRoot: packageRoot };
178131
- }
178132
- function sourceRootPath() {
178133
- return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
178134
- }
178135
- function distRootPath() {
178136
- return scannerRootsFromModuleUrl(import.meta.url).distRoot;
178137
- }
178138
- function scannerUnavailableDiagnostic(language, reason) {
178139
- const label = language.charAt(0).toUpperCase() + language.slice(1);
178140
- return {
178141
- severity: "error",
178142
- code: "code_scanner_unavailable",
178143
- language,
178144
- target: language,
178145
- message: `${label} scanner worker is unavailable: ${reason}`
178146
- };
178147
- }
178148
-
178149
- // src/scan/code/worker/scanner-worker.ts
178150
- var import__2020 = __toESM(require_2020(), 1);
178151
- import { spawnSync } from "node:child_process";
178152
- import { mkdirSync } from "node:fs";
178153
- import { tmpdir } from "node:os";
178154
- import { dirname as dirname2, join as join6 } from "node:path";
178155
178262
  // schemas/common-output.schema.json
178156
178263
  var common_output_schema_default = {
178157
178264
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -178199,7 +178306,17 @@ var common_output_schema_default = {
178199
178306
  target: { type: "string" },
178200
178307
  language: {
178201
178308
  type: "string",
178202
- enum: ["typescript", "swift", "dart", "rust", "go"]
178309
+ enum: [
178310
+ "typescript",
178311
+ "swift",
178312
+ "dart",
178313
+ "rust",
178314
+ "go",
178315
+ "javascript",
178316
+ "python",
178317
+ "ruby",
178318
+ "java"
178319
+ ]
178203
178320
  },
178204
178321
  source: { type: "string" },
178205
178322
  message: { type: "string" },
@@ -178219,7 +178336,7 @@ var scanner_worker_schema_default = {
178219
178336
  $defs: {
178220
178337
  language: {
178221
178338
  type: "string",
178222
- enum: ["typescript", "swift", "dart", "rust", "go"]
178339
+ enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
178223
178340
  },
178224
178341
  codeSymbol: {
178225
178342
  type: "object",
@@ -178335,6 +178452,8 @@ var scanner_worker_schema_default = {
178335
178452
  };
178336
178453
 
178337
178454
  // src/scan/code/worker/scanner-worker.ts
178455
+ var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
178456
+ var KILLED_WORKER_GRACE_MS = 500;
178338
178457
  function createLazyWorkerResponseValidator(compile) {
178339
178458
  let validator;
178340
178459
  return () => {
@@ -178352,15 +178471,29 @@ function compileWorkerResponseValidator() {
178352
178471
  $ref: "#/$defs/response"
178353
178472
  });
178354
178473
  }
178355
- function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178356
- const processResult = run({
178474
+ function invokeScannerWorker(request, command, run = runScannerWorkerProcess, stripEnv = []) {
178475
+ return interpretWorkerProcess(request, command, run(processInput(request, command, stripEnv)));
178476
+ }
178477
+ function invokeScannerWorkerAsync(request, command, runAsync = runScannerWorkerProcessAsync, stripEnv = []) {
178478
+ const run = runAsync(processInput(request, command, stripEnv));
178479
+ return {
178480
+ promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178481
+ cancel: () => run.cancel()
178482
+ };
178483
+ }
178484
+ function processInput(request, command, stripEnv) {
178485
+ return {
178357
178486
  command,
178358
- stdin: JSON.stringify(request)
178359
- });
178487
+ stdin: JSON.stringify(request),
178488
+ stripEnv,
178489
+ timeoutMs: workerTimeoutMs(request.files.length)
178490
+ };
178491
+ }
178492
+ function interpretWorkerProcess(request, command, processResult) {
178360
178493
  if (!processResult.ok) {
178361
178494
  return {
178362
178495
  ok: false,
178363
- diagnostic: scannerUnavailableDiagnostic2(request.language, processResult.error, command[0]),
178496
+ diagnostic: processResult.kind === "execution" ? scannerFailedDiagnostic(request.language, reasonOf(processResult.error)) : scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
178364
178497
  stderr: processResult.stderr
178365
178498
  };
178366
178499
  }
@@ -178401,43 +178534,203 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178401
178534
  }
178402
178535
  function clangModuleCachePath() {
178403
178536
  const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
178404
- return join6(tmpdir(), `docbridge-clang-module-cache-${owner}`);
178537
+ return join4(tmpdir(), `docbridge-clang-module-cache-${owner}`);
178538
+ }
178539
+ function workerProcessEnv(stripEnv = [], platform = process.platform) {
178540
+ const moduleCachePath = clangModuleCachePath();
178541
+ mkdirSync(moduleCachePath, { recursive: true });
178542
+ const fold = platform === "win32" ? (name) => name.toUpperCase() : (name) => name;
178543
+ const stripped = new Set(stripEnv.map(fold));
178544
+ const env = {};
178545
+ for (const [name, value] of Object.entries(process.env)) {
178546
+ if (value !== undefined && !stripped.has(fold(name))) {
178547
+ env[name] = value;
178548
+ }
178549
+ }
178550
+ env.CLANG_MODULE_CACHE_PATH = moduleCachePath;
178551
+ return env;
178552
+ }
178553
+ function workerTimeoutMs(fileCount) {
178554
+ return 30000 + 1000 * fileCount;
178405
178555
  }
178406
178556
  function runScannerWorkerProcess(input) {
178557
+ const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178407
178558
  try {
178408
- const moduleCachePath = clangModuleCachePath();
178409
- mkdirSync(moduleCachePath, { recursive: true });
178410
178559
  const [executable = "", ...args] = input.command;
178411
178560
  const result = spawnSync(executable, args, {
178412
- env: {
178413
- ...process.env,
178414
- CLANG_MODULE_CACHE_PATH: moduleCachePath
178415
- },
178561
+ env: workerProcessEnv(input.stripEnv),
178416
178562
  input: input.stdin,
178417
178563
  encoding: "utf8",
178418
- maxBuffer: 1024 * 1024 * 1024
178564
+ maxBuffer: maxOutputBytes,
178565
+ killSignal: "SIGKILL",
178566
+ ...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
178419
178567
  });
178420
- const stderr = result.stderr ?? "";
178421
- if (result.error !== undefined) {
178422
- return { ok: false, error: result.error, stderr };
178423
- }
178424
- if (result.status === null) {
178425
- return {
178426
- ok: false,
178427
- error: new Error(`worker terminated by signal ${result.signal ?? "unknown"}`),
178428
- stderr
178429
- };
178568
+ return syncWorkerProcessResult(result, input.timeoutMs, maxOutputBytes);
178569
+ } catch (error) {
178570
+ return { ok: false, kind: "start", error, stderr: "" };
178571
+ }
178572
+ }
178573
+ function syncWorkerProcessResult(result, timeoutMs, maxOutputBytes) {
178574
+ const stderr = result.stderr ?? "";
178575
+ if (result.error !== undefined) {
178576
+ return syncSpawnFailure(result.error, workerStarted(result), timeoutMs, maxOutputBytes, stderr);
178577
+ }
178578
+ if (Buffer.byteLength(result.stdout ?? "") + Buffer.byteLength(stderr) > maxOutputBytes) {
178579
+ return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178580
+ }
178581
+ if (typeof result.status !== "number") {
178582
+ return { ok: false, kind: "execution", error: signalError(result.signal), stderr };
178583
+ }
178584
+ return { ok: true, exitCode: result.status, stdout: result.stdout ?? "", stderr };
178585
+ }
178586
+ function runScannerWorkerProcessAsync(input) {
178587
+ const run = deferred();
178588
+ let settled = false;
178589
+ let timer;
178590
+ let grace;
178591
+ const settle = (complete) => {
178592
+ if (settled) {
178593
+ return;
178430
178594
  }
178431
- return {
178432
- ok: true,
178433
- exitCode: result.status,
178434
- stdout: result.stdout ?? "",
178435
- stderr
178436
- };
178595
+ settled = true;
178596
+ clearTimeout(timer);
178597
+ clearTimeout(grace);
178598
+ complete();
178599
+ };
178600
+ const ownGroup = process.platform !== "win32";
178601
+ let child;
178602
+ try {
178603
+ const [executable = "", ...args] = input.command;
178604
+ child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
178437
178605
  } catch (error) {
178438
- return { ok: false, error, stderr: "" };
178606
+ settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
178607
+ return { promise: run.promise, cancel: () => {
178608
+ return;
178609
+ } };
178610
+ }
178611
+ const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178612
+ const budget = { bytes: 0, limit: maxOutputBytes };
178613
+ const stdout = new OutputCollector(budget);
178614
+ const stderr = new OutputCollector(budget);
178615
+ let failure;
178616
+ const kill = (reason) => {
178617
+ failure ??= reason;
178618
+ killWorker(child, ownGroup);
178619
+ grace ??= setTimeout(() => {
178620
+ closeStreams(child);
178621
+ settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
178622
+ }, KILLED_WORKER_GRACE_MS);
178623
+ };
178624
+ let spawned = false;
178625
+ child.on("spawn", () => {
178626
+ spawned = true;
178627
+ });
178628
+ child.on("error", (error) => {
178629
+ if (!spawned) {
178630
+ settle(() => run.resolve({ ok: false, kind: "start", error, stderr: stderr.text() }));
178631
+ return;
178632
+ }
178633
+ failure ??= error;
178634
+ });
178635
+ child.stdout.on("data", (chunk) => {
178636
+ if (!stdout.add(chunk)) {
178637
+ kill(outputLimitError(maxOutputBytes));
178638
+ }
178639
+ });
178640
+ child.stderr.on("data", (chunk) => {
178641
+ if (!stderr.add(chunk)) {
178642
+ kill(outputLimitError(maxOutputBytes));
178643
+ }
178644
+ });
178645
+ child.stdin.on("error", () => {
178646
+ return;
178647
+ });
178648
+ child.on("close", (code, signal) => {
178649
+ settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
178650
+ });
178651
+ child.stdin.end(input.stdin);
178652
+ const { timeoutMs } = input;
178653
+ if (timeoutMs !== undefined) {
178654
+ timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
178655
+ }
178656
+ return {
178657
+ promise: run.promise,
178658
+ cancel() {
178659
+ settle(() => {
178660
+ killWorker(child, ownGroup);
178661
+ closeStreams(child);
178662
+ run.reject(abortError());
178663
+ });
178664
+ }
178665
+ };
178666
+ }
178667
+ function killWorker(child, ownGroup) {
178668
+ if (ownGroup && child.pid !== undefined) {
178669
+ try {
178670
+ process.kill(-child.pid, "SIGKILL");
178671
+ return;
178672
+ } catch {}
178673
+ }
178674
+ child.kill("SIGKILL");
178675
+ }
178676
+ function closeStreams(child) {
178677
+ child.stdin.destroy();
178678
+ child.stdout.destroy();
178679
+ child.stderr.destroy();
178680
+ }
178681
+
178682
+ class OutputCollector {
178683
+ budget;
178684
+ chunks = [];
178685
+ constructor(budget) {
178686
+ this.budget = budget;
178687
+ }
178688
+ add(chunk) {
178689
+ this.budget.bytes += chunk.byteLength;
178690
+ if (this.budget.bytes > this.budget.limit) {
178691
+ return false;
178692
+ }
178693
+ this.chunks.push(chunk);
178694
+ return true;
178695
+ }
178696
+ text() {
178697
+ return Buffer.concat(this.chunks).toString("utf8");
178439
178698
  }
178440
178699
  }
178700
+ function closedProcessResult(code, signal, failure, stdout, stderr) {
178701
+ if (failure !== undefined) {
178702
+ return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
178703
+ }
178704
+ if (code === null) {
178705
+ return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
178706
+ }
178707
+ return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
178708
+ }
178709
+ function workerStarted(result) {
178710
+ return typeof result.status === "number" || typeof result.signal === "string" || typeof result.pid === "number" && result.pid > 0;
178711
+ }
178712
+ function syncSpawnFailure(error, started, timeoutMs, maxOutputBytes, stderr) {
178713
+ const code = error.code;
178714
+ if (code === "ETIMEDOUT") {
178715
+ return { ok: false, kind: "execution", error: timeoutError(timeoutMs ?? 0), stderr };
178716
+ }
178717
+ if (code === "ENOBUFS") {
178718
+ return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178719
+ }
178720
+ return { ok: false, kind: started ? "execution" : "start", error, stderr };
178721
+ }
178722
+ function signalError(signal) {
178723
+ const name = signal ?? "unknown";
178724
+ return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
178725
+ }
178726
+ function timeoutError(timeoutMs) {
178727
+ return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
178728
+ code: "ETIMEDOUT"
178729
+ });
178730
+ }
178731
+ function outputLimitError(maxOutputBytes) {
178732
+ return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
178733
+ }
178441
178734
  function validateWorkerResponse(value, request) {
178442
178735
  const validator = responseValidator();
178443
178736
  if (!validator(value)) {
@@ -178463,117 +178756,768 @@ function formatSchemaError(errors) {
178463
178756
  const path = error.instancePath || "/";
178464
178757
  return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
178465
178758
  }
178466
- function responseFilesMatchRequest(responseFiles, requestFiles) {
178467
- if (responseFiles.length !== requestFiles.length) {
178468
- return false;
178759
+ function responseFilesMatchRequest(responseFiles, requestFiles) {
178760
+ if (responseFiles.length !== requestFiles.length) {
178761
+ return false;
178762
+ }
178763
+ return responseFiles.every((file, index) => {
178764
+ if (!isRecord(file)) {
178765
+ return false;
178766
+ }
178767
+ return file.filePath === requestFiles[index]?.filePath;
178768
+ });
178769
+ }
178770
+ function isRecord(value) {
178771
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178772
+ }
178773
+ function scannerUnavailableDiagnostic(language, error, executable) {
178774
+ const label = languageLabel(language);
178775
+ return {
178776
+ severity: "error",
178777
+ code: "code_scanner_unavailable",
178778
+ language,
178779
+ target: language,
178780
+ message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
178781
+ };
178782
+ }
178783
+ function spawnFailureReason(error, executable) {
178784
+ const reason = reasonOf(error);
178785
+ if (!isExecDenied(error) || executable === undefined) {
178786
+ return reason;
178787
+ }
178788
+ return `${reason}; the scanner is executable but ${dirname(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
178789
+ }
178790
+ function isExecDenied(error) {
178791
+ if (!(error instanceof Error)) {
178792
+ return false;
178793
+ }
178794
+ const code = error.code;
178795
+ return code === "EACCES" || code === "EPERM";
178796
+ }
178797
+ function scannerFailedDiagnostic(language, reason) {
178798
+ const label = languageLabel(language);
178799
+ return {
178800
+ severity: "error",
178801
+ code: "code_scanner_failed",
178802
+ language,
178803
+ target: language,
178804
+ message: `${label} scanner worker failed: ${reason}`
178805
+ };
178806
+ }
178807
+ function languageLabel(language) {
178808
+ return language.charAt(0).toUpperCase() + language.slice(1);
178809
+ }
178810
+
178811
+ // src/scan/code/worker/runtime-probe.ts
178812
+ var PROBE_TIMEOUT_MS = 1e4;
178813
+ var PROBE_MAX_OUTPUT_BYTES = 64 * 1024;
178814
+ function probeRuntime(command, stripEnv, spawn2 = spawnRuntimeProbe) {
178815
+ const [executable = "", ...args] = command;
178816
+ return classifyProbe(spawn2(executable, [...args, "--probe"], {
178817
+ env: workerProcessEnv(stripEnv),
178818
+ timeout: PROBE_TIMEOUT_MS,
178819
+ maxBuffer: PROBE_MAX_OUTPUT_BYTES
178820
+ }));
178821
+ }
178822
+ function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcessAsync) {
178823
+ const run = runAsync({
178824
+ command: [...command, "--probe"],
178825
+ stdin: "",
178826
+ stripEnv,
178827
+ timeoutMs: PROBE_TIMEOUT_MS,
178828
+ maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
178829
+ });
178830
+ return {
178831
+ promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
178832
+ cancel: () => run.cancel()
178833
+ };
178834
+ }
178835
+ function spawnResultOf(result) {
178836
+ if (result.ok) {
178837
+ return { status: result.exitCode, signal: null, stdout: result.stdout, stderr: result.stderr };
178838
+ }
178839
+ const error = result.error instanceof Error ? result.error : new Error(reasonOf(result.error));
178840
+ const { signal } = error;
178841
+ if (typeof signal === "string") {
178842
+ return { status: null, signal, stdout: "", stderr: result.stderr };
178843
+ }
178844
+ return { status: null, signal: null, stdout: "", stderr: result.stderr, error };
178845
+ }
178846
+ function classifyProbe(result) {
178847
+ if (result.error !== undefined) {
178848
+ return classifySpawnError(result.error);
178849
+ }
178850
+ if (result.status === null) {
178851
+ return { kind: "failed", reason: `probe terminated by signal ${result.signal ?? "unknown"}` };
178852
+ }
178853
+ if (result.status !== 0) {
178854
+ const detail = firstLine(result.stderr);
178855
+ return {
178856
+ kind: "failed",
178857
+ reason: `probe exited with status ${result.status}${detail === "" ? "" : `: ${detail}`}`
178858
+ };
178859
+ }
178860
+ return parseProbeOutput(result.stdout);
178861
+ }
178862
+ function classifySpawnError(error) {
178863
+ const code = error.code;
178864
+ if (code === "ETIMEDOUT") {
178865
+ return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
178866
+ }
178867
+ if (code === "ENOBUFS") {
178868
+ return {
178869
+ kind: "failed",
178870
+ reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178871
+ };
178872
+ }
178873
+ return { kind: "unstartable", reason: reasonOf(error) };
178874
+ }
178875
+ function parseProbeOutput(stdout) {
178876
+ const text = stdout.trim();
178877
+ let value;
178878
+ try {
178879
+ value = JSON.parse(text);
178880
+ } catch {
178881
+ return malformed(text);
178882
+ }
178883
+ if (!isRecord2(value)) {
178884
+ return malformed(text);
178885
+ }
178886
+ if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
178887
+ return { kind: "ok", runtime: value.runtime, version: value.version };
178888
+ }
178889
+ if (value.ok === false && typeof value.reason === "string") {
178890
+ return { kind: "rejected", reason: value.reason };
178891
+ }
178892
+ return malformed(text);
178893
+ }
178894
+ function malformed(text) {
178895
+ const shown = text.length > 200 ? `${text.slice(0, 200)}...` : text;
178896
+ return { kind: "failed", reason: `malformed probe output: ${JSON.stringify(shown)}` };
178897
+ }
178898
+ function firstLine(text) {
178899
+ return text.trim().split(/\r?\n/u)[0] ?? "";
178900
+ }
178901
+ function isRecord2(value) {
178902
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178903
+ }
178904
+ function spawnRuntimeProbe(executable, args, options) {
178905
+ try {
178906
+ const result = spawnSync2(executable, args, {
178907
+ env: options.env,
178908
+ timeout: options.timeout,
178909
+ killSignal: "SIGKILL",
178910
+ maxBuffer: options.maxBuffer,
178911
+ input: "",
178912
+ encoding: "utf8",
178913
+ windowsHide: true
178914
+ });
178915
+ const stdout = result.stdout ?? "";
178916
+ const stderr = result.stderr ?? "";
178917
+ const error = result.error === undefined && Buffer.byteLength(stdout) + Buffer.byteLength(stderr) > options.maxBuffer ? Object.assign(new Error("probe output exceeded maxBuffer"), { code: "ENOBUFS" }) : result.error;
178918
+ return {
178919
+ status: result.status,
178920
+ signal: result.signal,
178921
+ stdout,
178922
+ stderr,
178923
+ ...error === undefined ? {} : { error }
178924
+ };
178925
+ } catch (error) {
178926
+ return {
178927
+ status: null,
178928
+ signal: null,
178929
+ stdout: "",
178930
+ stderr: "",
178931
+ error: error instanceof Error ? error : new Error(String(error))
178932
+ };
178933
+ }
178934
+ }
178935
+
178936
+ // src/scan/code/worker/scanner-executable.ts
178937
+ import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
178938
+ import { join as join6 } from "node:path";
178939
+
178940
+ // src/shared/package-root.ts
178941
+ import { existsSync, realpathSync } from "node:fs";
178942
+ import { dirname as dirname2, join as join5 } from "node:path";
178943
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
178944
+ function resolvePackageRoot(moduleUrl = import.meta.url) {
178945
+ const modulePath = fileURLToPath2(moduleUrl);
178946
+ let resolved;
178947
+ try {
178948
+ resolved = realpathSync(modulePath);
178949
+ } catch {
178950
+ resolved = modulePath;
178951
+ }
178952
+ let dir = dirname2(resolved);
178953
+ for (;; ) {
178954
+ if (existsSync(join5(dir, "templates", "skills"))) {
178955
+ return dir;
178956
+ }
178957
+ const parent = dirname2(dir);
178958
+ if (parent === dir) {
178959
+ return dir;
178960
+ }
178961
+ dir = parent;
178962
+ }
178963
+ }
178964
+
178965
+ // src/scan/code/worker/scanner-executable.ts
178966
+ var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
178967
+ var SCANNER_EXECUTABLE_NAMES = {
178968
+ swift: "docbridge-swift-scanner",
178969
+ dart: "docbridge_dart_scanner",
178970
+ rust: "docbridge-rust-scanner",
178971
+ go: "docbridge-go-scanner"
178972
+ };
178973
+ function scannerPlatformKey() {
178974
+ return `${process.platform}-${process.arch}`;
178975
+ }
178976
+ function scannerExecutableName(language) {
178977
+ return SCANNER_EXECUTABLE_NAMES[language];
178978
+ }
178979
+ function resolveScannerWorkerCommand(language, options = {}) {
178980
+ const platformKey = options.platformKey ?? scannerPlatformKey();
178981
+ const platformSupported = isSupportedScannerPlatformKey(platformKey);
178982
+ const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
178983
+ const found = candidates.find((candidate) => existsSync2(candidate));
178984
+ if (found !== undefined) {
178985
+ const repair = ensureExecutable(found, options.chmod ?? chmodSync);
178986
+ if (!repair.ok) {
178987
+ return { ok: false, diagnostic: scannerUnavailableDiagnostic2(language, repair.reason) };
178988
+ }
178989
+ return { ok: true, command: [found] };
178990
+ }
178991
+ if (!platformSupported) {
178992
+ return {
178993
+ ok: false,
178994
+ diagnostic: scannerUnavailableDiagnostic2(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178995
+ };
178996
+ }
178997
+ return {
178998
+ ok: false,
178999
+ diagnostic: scannerUnavailableDiagnostic2(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
179000
+ };
179001
+ }
179002
+ function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
179003
+ const sourceRoot = options.sourceRoot ?? sourceRootPath();
179004
+ const distRoot = options.distRoot ?? distRootPath();
179005
+ const executable = scannerExecutableName(language);
179006
+ if (language === "swift") {
179007
+ return [
179008
+ join6(sourceRoot, "packages/swift-scanner/.build/release", executable),
179009
+ join6(sourceRoot, "packages/swift-scanner/.build/debug", executable),
179010
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179011
+ ];
179012
+ }
179013
+ if (language === "rust") {
179014
+ return [
179015
+ join6(sourceRoot, "packages/rust-scanner/target/release", executable),
179016
+ join6(sourceRoot, "packages/rust-scanner/target/debug", executable),
179017
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179018
+ ];
179019
+ }
179020
+ if (language === "go") {
179021
+ return [
179022
+ join6(sourceRoot, "packages/go-scanner/bin", executable),
179023
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179024
+ ];
179025
+ }
179026
+ return [
179027
+ join6(sourceRoot, "packages/dart-scanner/bin", executable),
179028
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179029
+ ];
179030
+ }
179031
+ function ensureExecutable(path, chmod) {
179032
+ let mode;
179033
+ try {
179034
+ mode = statSync(path).mode;
179035
+ } catch (error) {
179036
+ return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
179037
+ }
179038
+ if (isExecutableByThisProcess(path)) {
179039
+ return { ok: true };
179040
+ }
179041
+ try {
179042
+ chmod(path, mode | 73);
179043
+ } catch (error) {
179044
+ return {
179045
+ ok: false,
179046
+ reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
179047
+ };
179048
+ }
179049
+ return { ok: true };
179050
+ }
179051
+ function isExecutableByThisProcess(path) {
179052
+ try {
179053
+ accessSync(path, constants.X_OK);
179054
+ return true;
179055
+ } catch {
179056
+ return false;
179057
+ }
179058
+ }
179059
+ function formatMode(mode) {
179060
+ return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
179061
+ }
179062
+ function isSupportedScannerPlatformKey(platformKey) {
179063
+ return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
179064
+ }
179065
+ function scannerRootsFromModuleUrl(moduleUrl) {
179066
+ const packageRoot = resolvePackageRoot(moduleUrl);
179067
+ return { distRoot: join6(packageRoot, "dist"), sourceRoot: packageRoot };
179068
+ }
179069
+ function sourceRootPath() {
179070
+ return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
179071
+ }
179072
+ function distRootPath() {
179073
+ return scannerRootsFromModuleUrl(import.meta.url).distRoot;
179074
+ }
179075
+ function scannerUnavailableDiagnostic2(language, reason) {
179076
+ const label = language.charAt(0).toUpperCase() + language.slice(1);
179077
+ return {
179078
+ severity: "error",
179079
+ code: "code_scanner_unavailable",
179080
+ language,
179081
+ target: language,
179082
+ message: `${label} scanner worker is unavailable: ${reason}`
179083
+ };
179084
+ }
179085
+
179086
+ // src/scan/code/worker/runtime-worker.ts
179087
+ var RUNTIME_WORKERS = {
179088
+ python: {
179089
+ label: "Python",
179090
+ runtimeName: "CPython",
179091
+ runtimeId: "cpython",
179092
+ floor: [3, 10],
179093
+ floorText: "3.10",
179094
+ candidates: (platform) => platform === "win32" ? [["py", "-3"], ["python"]] : [["python3"], ["python"]],
179095
+ flags: ["-I", "-S"],
179096
+ stripEnv: ["PYTHONPATH", "PYTHONSTARTUP", "PYTHONHOME", "PYTHONSAFEPATH"],
179097
+ entrypoint: {
179098
+ source: "packages/python-scanner/docbridge_python_scanner.py",
179099
+ dist: "workers/python/docbridge_python_scanner.py"
179100
+ }
179101
+ },
179102
+ ruby: {
179103
+ label: "Ruby",
179104
+ runtimeName: "CRuby",
179105
+ runtimeId: "cruby",
179106
+ floor: [3, 3],
179107
+ floorText: "3.3",
179108
+ candidates: () => [["ruby"]],
179109
+ flags: ["--disable=gems,did_you_mean,error_highlight", "-W0"],
179110
+ stripEnv: ["RUBYOPT", "RUBYLIB", "PRISM_FFI_BACKEND"],
179111
+ entrypoint: {
179112
+ source: "packages/ruby-scanner/bin/docbridge-ruby-scanner",
179113
+ dist: "workers/ruby/bin/docbridge-ruby-scanner"
179114
+ }
179115
+ },
179116
+ java: {
179117
+ label: "Java",
179118
+ runtimeName: "JDK",
179119
+ runtimeId: "jdk",
179120
+ floor: [17, 0],
179121
+ floorText: "17",
179122
+ candidates: () => [["java"]],
179123
+ flags: ["-Xshare:auto", "-XX:TieredStopAtLevel=1", "-XX:+UseSerialGC", "-jar"],
179124
+ stripEnv: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS"],
179125
+ entrypoint: {
179126
+ source: "packages/java-scanner/build/docbridge-java-scanner.jar",
179127
+ dist: "workers/java/docbridge-java-scanner.jar"
179128
+ },
179129
+ buildHint: "run `just build-java-scanner` in a source checkout"
179130
+ }
179131
+ };
179132
+ var probeCache = new Map;
179133
+ var probeCacheGeneration = 0;
179134
+ function clearRuntimeProbeCache() {
179135
+ probeCache.clear();
179136
+ probeCacheGeneration += 1;
179137
+ }
179138
+ function rememberProbe(key, generation, outcome) {
179139
+ if (generation === probeCacheGeneration) {
179140
+ probeCache.set(key, outcome);
179141
+ }
179142
+ }
179143
+ function resolveRuntimeWorkerCommand(language, options) {
179144
+ const env = options.env ?? process.env;
179145
+ const probe = options.probe ?? probeRuntime;
179146
+ const steps = resolutionSteps(language, options, env);
179147
+ let step = steps.next();
179148
+ while (step.done !== true) {
179149
+ step = steps.next(cachedProbe(step.value, env, probe));
179150
+ }
179151
+ return step.value;
179152
+ }
179153
+ function resolveRuntimeWorkerCommandAsync(language, options) {
179154
+ const env = options.env ?? process.env;
179155
+ const probeAsync = options.probeAsync ?? probeRuntimeAsync;
179156
+ return cancelableSequence(async (run) => {
179157
+ const steps = resolutionSteps(language, options, env);
179158
+ let step = steps.next();
179159
+ while (step.done !== true) {
179160
+ const request = step.value;
179161
+ step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
179162
+ }
179163
+ return step.value;
179164
+ });
179165
+ }
179166
+ function* resolutionSteps(language, options, env) {
179167
+ const spec = RUNTIME_WORKERS[language];
179168
+ const entrypoint = findEntrypoint(spec, options);
179169
+ if (!entrypoint.ok) {
179170
+ return failure(spec, language, "code_scanner_unavailable", entrypoint.reason);
179171
+ }
179172
+ const platform = options.platform ?? process.platform;
179173
+ const commandFor = (runtime) => [...runtime, ...spec.flags, entrypoint.path];
179174
+ const usable = (command, runtime, verdict) => ({
179175
+ ok: true,
179176
+ command,
179177
+ stripEnv: spec.stripEnv,
179178
+ runtime: [verdict.runtime, verdict.version, locateExecutable(runtime[0] ?? "", env, platform)]
179179
+ });
179180
+ const override = explicitOverride(language, options, env);
179181
+ if (override !== undefined) {
179182
+ const runtime = override.argv.map((part, index) => index === 0 ? resolveExecutable(part, options.projectRoot) : part);
179183
+ const command = commandFor(runtime);
179184
+ const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
179185
+ if (verdict.ok) {
179186
+ return usable(command, runtime, verdict);
179187
+ }
179188
+ return failure(spec, language, verdict.code, `${override.source} (${runtime.join(" ")}) ${verdict.reason}; no other runtime is tried ` + `while ${override.source} is set`);
179189
+ }
179190
+ const rejections = [];
179191
+ for (const runtime of spec.candidates(platform)) {
179192
+ const command = commandFor(runtime);
179193
+ const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
179194
+ if (verdict.ok) {
179195
+ return usable(command, runtime, verdict);
179196
+ }
179197
+ rejections.push({ runtime, verdict });
179198
+ }
179199
+ const outcomes = rejections.map(({ runtime, verdict }) => `${runtime.join(" ")} ${verdict.reason}`).join("; ");
179200
+ const code = rejections.find(({ verdict }) => verdict.startable)?.verdict.code ?? "code_scanner_unavailable";
179201
+ return failure(spec, language, code, `no usable ${spec.runtimeName} ${spec.floorText} or later found: ${outcomes}. Install ` + `${spec.runtimeName} ${spec.floorText} or later, or set scanners.${language}.command or ` + `${environmentVariable(language)}`);
179202
+ }
179203
+ function findEntrypoint(spec, options) {
179204
+ const roots = scannerRootsFromModuleUrl(import.meta.url);
179205
+ const source = join7(options.sourceRoot ?? roots.sourceRoot, spec.entrypoint.source);
179206
+ const dist = join7(options.distRoot ?? roots.distRoot, spec.entrypoint.dist);
179207
+ const found = [source, dist].find((path) => existsSync3(path));
179208
+ if (found !== undefined) {
179209
+ return { ok: true, path: found };
179210
+ }
179211
+ const hint = spec.buildHint === undefined ? "" : `; ${spec.buildHint}`;
179212
+ return {
179213
+ ok: false,
179214
+ reason: `the bundled worker is missing; looked for ${source} and ${dist}${hint}`
179215
+ };
179216
+ }
179217
+ function explicitOverride(language, options, env) {
179218
+ if (options.command !== undefined && options.command.length > 0) {
179219
+ return { source: `scanners.${language}.command`, argv: options.command };
179220
+ }
179221
+ const variable = environmentVariable(language);
179222
+ const value = env[variable];
179223
+ if (value !== undefined && value !== "") {
179224
+ return { source: variable, argv: [value] };
179225
+ }
179226
+ return;
179227
+ }
179228
+ function environmentVariable(language) {
179229
+ return `DOCBRIDGE_${language.toUpperCase()}_RUNTIME`;
179230
+ }
179231
+ function resolveExecutable(executable, projectRoot) {
179232
+ if (isAbsolute2(executable) || !/[/\\]/u.test(executable)) {
179233
+ return executable;
179234
+ }
179235
+ return resolve2(projectRoot, executable);
179236
+ }
179237
+ function locateExecutable(executable, env, platform) {
179238
+ const candidates = /[/\\]/u.test(executable) ? [executable] : pathCandidates(executable, env, platform);
179239
+ const found = candidates.find((candidate) => isExecutableFile(candidate, platform));
179240
+ if (found === undefined) {
179241
+ return executable;
178469
179242
  }
178470
- return responseFiles.every((file, index) => {
178471
- if (!isRecord(file)) {
179243
+ try {
179244
+ return realpathSync2(found);
179245
+ } catch {
179246
+ return found;
179247
+ }
179248
+ }
179249
+ function pathCandidates(name, env, platform) {
179250
+ const windows = platform === "win32";
179251
+ const directories = (env.PATH ?? "").split(windows ? ";" : ":").filter((dir) => dir !== "");
179252
+ const extensions = windows && extname(name) === "" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
179253
+ return directories.flatMap((directory) => extensions.map((extension) => join7(directory, `${name}${extension}`)));
179254
+ }
179255
+ function isExecutableFile(path, platform) {
179256
+ try {
179257
+ if (!statSync2(path).isFile()) {
178472
179258
  return false;
178473
179259
  }
178474
- return file.filePath === requestFiles[index]?.filePath;
178475
- });
179260
+ if (platform !== "win32") {
179261
+ accessSync2(path, constants2.X_OK);
179262
+ }
179263
+ return true;
179264
+ } catch {
179265
+ return false;
179266
+ }
178476
179267
  }
178477
- function isRecord(value) {
178478
- return typeof value === "object" && value !== null && !Array.isArray(value);
179268
+ function cachedProbe({ command, stripEnv }, env, probe) {
179269
+ const key = probeCacheKey(command, env);
179270
+ const cached = probeCache.get(key);
179271
+ if (cached !== undefined) {
179272
+ return cached;
179273
+ }
179274
+ const generation = probeCacheGeneration;
179275
+ const outcome = probe(command, stripEnv);
179276
+ rememberProbe(key, generation, outcome);
179277
+ return outcome;
178479
179278
  }
178480
- function scannerUnavailableDiagnostic2(language, error, executable) {
178481
- const label = languageLabel(language);
178482
- return {
178483
- severity: "error",
178484
- code: "code_scanner_unavailable",
178485
- language,
178486
- target: language,
178487
- message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
178488
- };
179279
+ function cachedProbeAsync({ command, stripEnv }, env, probeAsync) {
179280
+ const key = probeCacheKey(command, env);
179281
+ const cached = probeCache.get(key);
179282
+ if (cached !== undefined) {
179283
+ return settledCancelable(cached);
179284
+ }
179285
+ const generation = probeCacheGeneration;
179286
+ return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
179287
+ rememberProbe(key, generation, outcome);
179288
+ return outcome;
179289
+ });
178489
179290
  }
178490
- function spawnFailureReason(error, executable) {
178491
- const reason = reasonOf(error);
178492
- if (!isExecDenied(error) || executable === undefined) {
178493
- return reason;
179291
+ function probeCacheKey(command, env) {
179292
+ const docbridgeVariables = Object.entries(env).filter(([name]) => name.startsWith("DOCBRIDGE_")).toSorted(([left], [right]) => left.localeCompare(right));
179293
+ return JSON.stringify([command, env.PATH ?? "", docbridgeVariables]);
179294
+ }
179295
+ function judge(spec, outcome) {
179296
+ switch (outcome.kind) {
179297
+ case "unstartable":
179298
+ return rejected("code_scanner_unavailable", false, `could not be started: ${outcome.reason}`);
179299
+ case "failed":
179300
+ return rejected("code_scanner_failed", true, `failed its probe: ${outcome.reason}`);
179301
+ case "rejected":
179302
+ return rejected("code_scanner_unavailable", true, `is not a usable ${spec.runtimeName} ${spec.floorText} or later: ${outcome.reason}`);
179303
+ case "ok":
179304
+ return judgeVersion(spec, outcome.runtime, outcome.version);
178494
179305
  }
178495
- return `${reason}; the scanner is executable but ${dirname2(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
178496
179306
  }
178497
- function isExecDenied(error) {
178498
- if (!(error instanceof Error)) {
178499
- return false;
179307
+ function judgeVersion(spec, runtime, version) {
179308
+ if (runtime !== spec.runtimeId) {
179309
+ return rejected("code_scanner_unavailable", true, `reports runtime ${runtime} ${version}, not ${spec.runtimeName}`);
178500
179310
  }
178501
- const code = error.code;
178502
- return code === "EACCES" || code === "EPERM";
179311
+ const match = /^(\d+)(?:\.(\d+))?/u.exec(version);
179312
+ if (match === null) {
179313
+ return rejected("code_scanner_failed", true, `reported an unreadable version: ${version}`);
179314
+ }
179315
+ const major = Number(match[1]);
179316
+ const minor = Number(match[2] ?? "0");
179317
+ const [floorMajor, floorMinor] = spec.floor;
179318
+ if (major < floorMajor || major === floorMajor && minor < floorMinor) {
179319
+ return rejected("code_scanner_unavailable", true, `is ${spec.runtimeName} ${version}, below the ${spec.floorText} floor`);
179320
+ }
179321
+ return { ok: true, runtime, version };
178503
179322
  }
178504
- function scannerFailedDiagnostic(language, reason) {
178505
- const label = languageLabel(language);
179323
+ function rejected(code, startable, reason) {
179324
+ return { ok: false, code, startable, reason };
179325
+ }
179326
+ function failure(spec, language, code, reason) {
179327
+ const state = code === "code_scanner_unavailable" ? "is unavailable" : "failed";
178506
179328
  return {
178507
- severity: "error",
178508
- code: "code_scanner_failed",
178509
- language,
178510
- target: language,
178511
- message: `${label} scanner worker failed: ${reason}`
179329
+ ok: false,
179330
+ diagnostic: {
179331
+ severity: "error",
179332
+ code,
179333
+ language,
179334
+ target: language,
179335
+ message: `${spec.label} scanner worker ${state}: ${reason}`
179336
+ }
178512
179337
  };
178513
179338
  }
178514
- function languageLabel(language) {
178515
- return language.charAt(0).toUpperCase() + language.slice(1);
178516
- }
178517
179339
 
178518
179340
  // src/scan/code/dispatch.ts
178519
179341
  function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
179342
+ const { commandAsync } = adapterOptions;
179343
+ const bound = (context) => boundWorkerAdapter(language, workerCommand(command(context)), adapterOptions).adapter;
179344
+ const prepare = (context) => mapCancelable(commandAsync?.(context) ?? settledCancelable(command(context)), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
179345
+ const scanBatchAsync = (files, options, context) => {
179346
+ if (files.length === 0) {
179347
+ return settledCancelable([]);
179348
+ }
179349
+ if (commandAsync === undefined) {
179350
+ return bound(context).scanFilesAsync(files, options, context);
179351
+ }
179352
+ return cancelableSequence(async (step) => {
179353
+ const { adapter } = await step(() => prepare(context));
179354
+ return step(() => adapter.scanFilesAsync(files, options, context));
179355
+ });
179356
+ };
178520
179357
  return {
179358
+ language,
179359
+ scanFile: (filePath, content, options, context) => bound(context).scanFile(filePath, content, options, context),
179360
+ scanFiles: (files, options, context) => files.length === 0 ? [] : bound(context).scanFiles(files, options, context),
179361
+ scanFilesAsync: scanBatchAsync,
179362
+ prepare
179363
+ };
179364
+ }
179365
+ function boundWorkerAdapter(language, resolved, adapterOptions) {
179366
+ const argv = resolved.ok ? resolved.command : [];
179367
+ const runtime = resolved.ok ? resolved.runtime : [];
179368
+ const adapter = {
178521
179369
  language,
178522
179370
  scanFile(filePath, content, options, context) {
178523
- const commandResolution = normalizeScannerCommand(command(context.projectRoot));
178524
- if (!commandResolution.ok) {
178525
- return {
178526
- ...emptyScan(language, filePath),
178527
- diagnostics: [fileScopedScannerDiagnostic(commandResolution.diagnostic, filePath)]
178528
- };
179371
+ const [scan] = adapter.scanFiles([{ filePath, content }], options, context);
179372
+ return scan ?? emptyScan(language, filePath);
179373
+ },
179374
+ scanFiles(files, options, context) {
179375
+ if (files.length === 0) {
179376
+ return [];
178529
179377
  }
178530
- const result = invokeScannerWorker({
178531
- schemaVersion: 1,
178532
- requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
178533
- language,
178534
- projectRoot: context.projectRoot,
178535
- files: [{ filePath, content }],
178536
- options
178537
- }, commandResolution.command, adapterOptions.run);
178538
- if (result.ok) {
178539
- const scan = result.codeFiles[0];
178540
- return scan ?? emptyScan(language, filePath);
179378
+ if (!resolved.ok) {
179379
+ return failedBatch(language, files, resolved.diagnostic);
178541
179380
  }
178542
- return {
178543
- ...emptyScan(language, filePath),
178544
- diagnostics: [fileScopedScannerDiagnostic(result.diagnostic, filePath)]
178545
- };
178546
- }
179381
+ const result = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
179382
+ return result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic);
179383
+ },
179384
+ scanFilesAsync(files, options, context) {
179385
+ if (files.length === 0) {
179386
+ return settledCancelable([]);
179387
+ }
179388
+ if (!resolved.ok) {
179389
+ return settledCancelable(failedBatch(language, files, resolved.diagnostic));
179390
+ }
179391
+ const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
179392
+ return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
179393
+ },
179394
+ prepare: () => settledCancelable({ argv, runtime, adapter })
178547
179395
  };
179396
+ return { argv, runtime, adapter };
178548
179397
  }
178549
179398
  function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
178550
- const codeFiles = [];
178551
- const diagnostics = [];
179399
+ const plan = planCodeScan(files, codeInclude, read, options.onContent);
179400
+ const context = scanContext(projectRoot, options);
179401
+ for (const batch of plan.batches) {
179402
+ const adapter = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179403
+ fillBatch(plan, batch, adapter.scanFiles(batch.files, batch.options, context));
179404
+ }
179405
+ return assemblePlan(plan);
179406
+ }
179407
+ function scanContext(projectRoot, options) {
179408
+ return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
179409
+ }
179410
+ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
179411
+ const plan = planCodeScan(files, codeInclude, read, options.onContent);
179412
+ const context = scanContext(projectRoot, options);
179413
+ return cancelableSequence(async (step) => {
179414
+ const cache = new Map;
179415
+ for (const batch of plan.batches) {
179416
+ const base = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179417
+ const prepare = base.prepare?.bind(base);
179418
+ const { argv, runtime, adapter } = prepare === undefined ? { argv: [], adapter: base } : await step(() => prepare(context));
179419
+ const keys = batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
179420
+ const cached = keys.map((key) => options.cache?.get(key));
179421
+ const missing = batch.files.filter((_, index) => cached[index] === undefined);
179422
+ const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
179423
+ let next = 0;
179424
+ const results = cached.map((hit) => hit ?? scanned[next++]);
179425
+ fillBatch(plan, batch, results);
179426
+ results.forEach((scan, index) => {
179427
+ const key = keys[index];
179428
+ if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
179429
+ cache.set(key, scan);
179430
+ }
179431
+ });
179432
+ }
179433
+ return { ...assemblePlan(plan), cache };
179434
+ });
179435
+ }
179436
+ function scanFilesAsync(adapter, files, options, context) {
179437
+ return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
179438
+ }
179439
+ var builtInAdapters = {
179440
+ typescript: typeScriptAdapter,
179441
+ swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
179442
+ dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
179443
+ rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
179444
+ go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go")),
179445
+ javascript: javaScriptAdapter,
179446
+ python: runtimeWorkerAdapter("python"),
179447
+ ruby: runtimeWorkerAdapter("ruby"),
179448
+ java: runtimeWorkerAdapter("java")
179449
+ };
179450
+ function runtimeWorkerAdapter(language) {
179451
+ const resolution = ({ projectRoot, scanners }) => {
179452
+ const command = scanners?.[language]?.command;
179453
+ return command === undefined ? { projectRoot } : { projectRoot, command };
179454
+ };
179455
+ return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
179456
+ }
179457
+ function planCodeScan(files, codeInclude, read, onContent) {
179458
+ const slots = [];
179459
+ const batches = new Map;
178552
179460
  for (const { language, relPath } of files) {
178553
179461
  const result = read(relPath);
178554
179462
  if (!result.ok) {
178555
- diagnostics.push(result.diagnostic);
179463
+ slots.push({ read: false, diagnostic: result.diagnostic });
178556
179464
  continue;
178557
179465
  }
178558
- options.onContent?.(relPath, result.content);
178559
- const adapter = options.adapters?.[language] ?? builtInAdapters[language];
178560
- const entry = codeInclude[language];
178561
- const scanOptions = entry?.visibility !== undefined ? { visibility: entry.visibility } : {};
178562
- const scan = adapter.scanFile(relPath, result.content, scanOptions, {
178563
- projectRoot
178564
- });
179466
+ onContent?.(relPath, result.content);
179467
+ let batch = batches.get(language);
179468
+ if (batch === undefined) {
179469
+ batch = { language, options: scanOptionsFor(codeInclude, language), files: [], slots: [] };
179470
+ batches.set(language, batch);
179471
+ }
179472
+ batch.files.push({ filePath: relPath, content: result.content });
179473
+ batch.slots.push(slots.length);
179474
+ slots.push({ read: true, language, relPath });
179475
+ }
179476
+ return { slots, batches: [...batches.values()] };
179477
+ }
179478
+ function fillBatch(plan, batch, results) {
179479
+ batch.slots.forEach((slotIndex, resultIndex) => {
179480
+ const slot = plan.slots[slotIndex];
179481
+ const scan = results[resultIndex];
179482
+ if (slot?.read === true && scan !== undefined) {
179483
+ slot.scan = scan;
179484
+ }
179485
+ });
179486
+ }
179487
+ function assemblePlan(plan) {
179488
+ const codeFiles = [];
179489
+ const diagnostics = [];
179490
+ for (const slot of plan.slots) {
179491
+ if (!slot.read) {
179492
+ diagnostics.push(slot.diagnostic);
179493
+ continue;
179494
+ }
179495
+ const scan = slot.scan ?? emptyScan(slot.language, slot.relPath);
178565
179496
  diagnostics.push(...scan.diagnostics);
178566
179497
  codeFiles.push(scan);
178567
179498
  }
178568
179499
  return { codeFiles, diagnostics };
178569
179500
  }
178570
- var builtInAdapters = {
178571
- typescript: typeScriptAdapter,
178572
- swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
178573
- dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
178574
- rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
178575
- go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go"))
178576
- };
179501
+ function scanOptionsFor(codeInclude, language) {
179502
+ const visibility = codeInclude[language]?.visibility;
179503
+ return visibility !== undefined ? { visibility } : {};
179504
+ }
179505
+ function workerRequest(language, files, options, context, adapterOptions) {
179506
+ return {
179507
+ schemaVersion: 1,
179508
+ requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
179509
+ language,
179510
+ projectRoot: context.projectRoot,
179511
+ files: files.map(({ filePath, content }) => ({ filePath, content })),
179512
+ options
179513
+ };
179514
+ }
179515
+ function failedBatch(language, files, diagnostic) {
179516
+ return files.map(({ filePath }) => ({
179517
+ ...emptyScan(language, filePath),
179518
+ diagnostics: [{ ...diagnostic, target: filePath }]
179519
+ }));
179520
+ }
178577
179521
  function emptyScan(language, filePath) {
178578
179522
  return {
178579
179523
  language,
@@ -178584,11 +179528,16 @@ function emptyScan(language, filePath) {
178584
179528
  diagnostics: []
178585
179529
  };
178586
179530
  }
178587
- function fileScopedScannerDiagnostic(diagnostic, filePath) {
178588
- return { ...diagnostic, target: filePath };
178589
- }
178590
- function normalizeScannerCommand(value) {
178591
- return Array.isArray(value) ? { ok: true, command: value } : value;
179531
+ function workerCommand(value) {
179532
+ if (Array.isArray(value)) {
179533
+ return { ok: true, command: value, stripEnv: [], runtime: [] };
179534
+ }
179535
+ return value.ok ? {
179536
+ ok: true,
179537
+ command: value.command,
179538
+ stripEnv: value.stripEnv ?? [],
179539
+ runtime: value.runtime ?? []
179540
+ } : value;
178592
179541
  }
178593
179542
 
178594
179543
  // src/scan/markdown/markdown.ts
@@ -178823,6 +179772,42 @@ function flushDanglingWith(pending, diagnostics, message) {
178823
179772
 
178824
179773
  // src/query/project-scan.ts
178825
179774
  function scanProject(options) {
179775
+ const loaded = loadProjectScan(options, options.keepContent === true);
179776
+ if (!loaded.ok) {
179777
+ return loaded;
179778
+ }
179779
+ const { inputs } = loaded;
179780
+ const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, codeScanOptions(inputs, options));
179781
+ const docReads = readDocFiles(inputs, options);
179782
+ return { ok: true, scan: finishProjectScan(inputs, codeScan, docReads, options.buildGraph) };
179783
+ }
179784
+ function scanProjectAsync(options) {
179785
+ const loaded = loadProjectScan(options, true);
179786
+ if (!loaded.ok) {
179787
+ return cancelableSequence(async () => loaded);
179788
+ }
179789
+ const { inputs } = loaded;
179790
+ const fingerprint = JSON.stringify(inputs.config);
179791
+ const sameConfiguration = options.cache.fingerprint === fingerprint;
179792
+ if (!sameConfiguration && options.cache.fingerprint !== "") {
179793
+ options.onConfigurationChange?.();
179794
+ }
179795
+ const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179796
+ ...codeScanOptions(inputs, options),
179797
+ ...sameConfiguration ? { cache: options.cache.entries } : {}
179798
+ });
179799
+ const docReads = readDocFiles(inputs, options);
179800
+ return cancelableSequence(async (step) => {
179801
+ const { cache, ...code } = await step(codeScan);
179802
+ const scan = finishProjectScan(inputs, code, docReads, true);
179803
+ return {
179804
+ ok: true,
179805
+ scan,
179806
+ cache: { fingerprint, entries: cache }
179807
+ };
179808
+ });
179809
+ }
179810
+ function loadProjectScan(options, keepContent) {
178826
179811
  const configResult = loadConfig(options.projectRoot);
178827
179812
  if (!configResult.ok) {
178828
179813
  return { ok: false, diagnostics: configResult.diagnostics };
@@ -178832,32 +179817,51 @@ function scanProject(options) {
178832
179817
  return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
178833
179818
  }
178834
179819
  const collectCode = options.collectCode ?? collectCodeFiles;
178835
- const collectDocs = options.collectDocs ?? collectFiles;
178836
- const readFile = options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath));
178837
- const diagnostics = [
178838
- ...configResult.diagnostics,
178839
- ...manifestResult.diagnostics
178840
- ];
178841
- const contentByFile = options.keepContent ? new Map : undefined;
178842
- const codeScan = scanCodeFiles(options.projectRoot, collectCode(options.projectRoot, configResult.config.include.code), configResult.config.include.code, readFile, {
179820
+ return {
179821
+ ok: true,
179822
+ inputs: {
179823
+ config: configResult.config,
179824
+ manifest: manifestResult.manifest,
179825
+ diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics],
179826
+ codeFiles: collectCode(options.projectRoot, configResult.config.include.code),
179827
+ readFile: options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath)),
179828
+ contentByFile: keepContent ? new Map : undefined
179829
+ }
179830
+ };
179831
+ }
179832
+ function codeScanOptions(inputs, options) {
179833
+ const { contentByFile } = inputs;
179834
+ const { scanners } = inputs.config;
179835
+ return {
178843
179836
  ...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
178844
- ...options.adapters === undefined ? {} : { adapters: options.adapters }
179837
+ ...options.adapters === undefined ? {} : { adapters: options.adapters },
179838
+ ...scanners === undefined ? {} : { scanners }
179839
+ };
179840
+ }
179841
+ function readDocFiles(inputs, options) {
179842
+ const collectDocs = options.collectDocs ?? collectFiles;
179843
+ return collectDocs(options.projectRoot, inputs.config.include.docs).map((relPath) => {
179844
+ const read = inputs.readFile(relPath);
179845
+ if (read.ok) {
179846
+ inputs.contentByFile?.set(relPath, read.content);
179847
+ }
179848
+ return { relPath, read };
178845
179849
  });
178846
- diagnostics.push(...codeScan.diagnostics);
179850
+ }
179851
+ function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
179852
+ const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
178847
179853
  const docFiles = [];
178848
- for (const relPath of collectDocs(options.projectRoot, configResult.config.include.docs)) {
178849
- const read = readFile(relPath);
179854
+ for (const { relPath, read } of docReads) {
178850
179855
  if (!read.ok) {
178851
179856
  diagnostics.push(read.diagnostic);
178852
179857
  continue;
178853
179858
  }
178854
- contentByFile?.set(relPath, read.content);
178855
179859
  const scan2 = scanMarkdown(relPath, read.content);
178856
179860
  diagnostics.push(...scan2.diagnostics);
178857
179861
  docFiles.push(scan2);
178858
179862
  }
178859
179863
  const applied = applyLinkManifest({
178860
- manifest: manifestResult.manifest,
179864
+ manifest: inputs.manifest,
178861
179865
  codeFiles: codeScan.codeFiles,
178862
179866
  docFiles,
178863
179867
  scanDiagnostics: diagnostics
@@ -178869,25 +179873,26 @@ function scanProject(options) {
178869
179873
  docFiles: applied.docFiles,
178870
179874
  diagnostics
178871
179875
  };
178872
- if (contentByFile !== undefined) {
178873
- scan.contentByFile = contentByFile;
179876
+ if (inputs.contentByFile !== undefined) {
179877
+ scan.contentByFile = inputs.contentByFile;
178874
179878
  }
178875
- if (options.buildGraph === true) {
179879
+ if (buildGraph === true) {
178876
179880
  scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
178877
179881
  }
178878
- return {
178879
- ok: true,
178880
- scan
178881
- };
179882
+ return scan;
178882
179883
  }
178883
179884
 
178884
179885
  // src/lsp/project.ts
178885
179886
  class Project {
178886
179887
  projectRoot;
179888
+ options;
178887
179889
  overlay = new Map;
179890
+ overlayRevision = 0;
178888
179891
  current = emptyState();
178889
- constructor(projectRoot) {
179892
+ cache = emptyCodeScanCache();
179893
+ constructor(projectRoot, options = {}) {
178890
179894
  this.projectRoot = projectRoot;
179895
+ this.options = options;
178891
179896
  }
178892
179897
  get root() {
178893
179898
  return this.projectRoot;
@@ -178897,48 +179902,57 @@ class Project {
178897
179902
  }
178898
179903
  setOverlay(relPath, content) {
178899
179904
  this.overlay.set(relPath, content);
179905
+ this.overlayRevision += 1;
178900
179906
  }
178901
179907
  clearOverlay(relPath) {
178902
179908
  this.overlay.delete(relPath);
179909
+ this.overlayRevision += 1;
178903
179910
  }
178904
179911
  resolve() {
178905
179912
  const outcome = scanProject({
178906
- projectRoot: this.projectRoot,
178907
- collectCode: (_projectRoot, include) => this.collectCode(include),
178908
- collectDocs: (_projectRoot, patterns) => this.collect(patterns, false),
178909
- readFile: (relPath) => this.readContent(relPath),
179913
+ ...this.scanSources(this.overlay),
178910
179914
  buildGraph: true,
178911
179915
  keepContent: true
178912
179916
  });
178913
- if (!outcome.ok) {
178914
- this.current = {
178915
- ...emptyState(),
178916
- diagnostics: sortDiagnostics(outcome.diagnostics)
178917
- };
179917
+ this.current = resolvedState(outcome);
179918
+ return this.current;
179919
+ }
179920
+ resolveAsync() {
179921
+ const revision = this.overlayRevision;
179922
+ const scan = scanProjectAsync({
179923
+ ...this.scanSources(new Map(this.overlay)),
179924
+ cache: this.cache,
179925
+ onConfigurationChange: clearRuntimeProbeCache
179926
+ });
179927
+ let cancelled = false;
179928
+ const promise = scan.promise.then((outcome) => {
179929
+ if (cancelled || revision !== this.overlayRevision) {
179930
+ throw abortError();
179931
+ }
179932
+ if (outcome.ok) {
179933
+ this.cache = outcome.cache;
179934
+ }
179935
+ this.current = resolvedState(outcome);
178918
179936
  return this.current;
178919
- }
178920
- const {
178921
- codeFiles,
178922
- docFiles,
178923
- diagnostics: scanDiagnostics,
178924
- contentByFile,
178925
- graph
178926
- } = outcome.scan;
178927
- const relationship = resolveLinks({
178928
- codeFiles,
178929
- docFiles,
178930
- scanDiagnostics,
178931
- audit: false
178932
179937
  });
178933
- this.current = {
178934
- graph,
178935
- index: buildPositionIndex(graph),
178936
- diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
178937
- contentByFile
179938
+ return {
179939
+ promise,
179940
+ cancel() {
179941
+ cancelled = true;
179942
+ scan.cancel();
179943
+ }
178938
179944
  };
178939
- return this.current;
178940
179945
  }
178941
- collectCode(codeInclude) {
179946
+ scanSources(overlay) {
179947
+ return {
179948
+ projectRoot: this.projectRoot,
179949
+ collectCode: (_projectRoot, include) => this.collectCode(include, overlay),
179950
+ collectDocs: (_projectRoot, patterns) => this.collectDocs(patterns, overlay),
179951
+ readFile: (relPath) => this.readContent(relPath, overlay),
179952
+ ...this.options.adapters === undefined ? {} : { adapters: this.options.adapters }
179953
+ };
179954
+ }
179955
+ collectCode(codeInclude, overlay) {
178942
179956
  const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
178943
179957
  const seen = new Set(onDisk.map((file) => file.relPath));
178944
179958
  const all = [...onDisk];
@@ -178947,11 +179961,11 @@ class Project {
178947
179961
  if (entry === undefined) {
178948
179962
  continue;
178949
179963
  }
178950
- for (const relPath of this.overlay.keys()) {
179964
+ for (const relPath of overlay.keys()) {
178951
179965
  if (seen.has(relPath)) {
178952
179966
  continue;
178953
179967
  }
178954
- if (language === "typescript" && relPath.endsWith(".d.ts")) {
179968
+ if (hasExcludedSuffix(language, relPath)) {
178955
179969
  continue;
178956
179970
  }
178957
179971
  if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
@@ -178962,22 +179976,19 @@ class Project {
178962
179976
  }
178963
179977
  return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
178964
179978
  }
178965
- readContent(relPath) {
178966
- const overlaid = this.overlay.get(relPath);
179979
+ readContent(relPath, overlay) {
179980
+ const overlaid = overlay.get(relPath);
178967
179981
  if (overlaid !== undefined) {
178968
179982
  return { ok: true, content: overlaid };
178969
179983
  }
178970
179984
  return readManagedFile(this.projectRoot, relPath);
178971
179985
  }
178972
- collect(patterns, isCode) {
179986
+ collectDocs(patterns, overlay) {
178973
179987
  const paths = new Set(collectFiles(this.projectRoot, patterns));
178974
- for (const relPath of this.overlay.keys()) {
179988
+ for (const relPath of overlay.keys()) {
178975
179989
  if (paths.has(relPath)) {
178976
179990
  continue;
178977
179991
  }
178978
- if (isCode && relPath.endsWith(".d.ts")) {
178979
- continue;
178980
- }
178981
179992
  if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
178982
179993
  paths.add(relPath);
178983
179994
  }
@@ -178985,6 +179996,19 @@ class Project {
178985
179996
  return [...paths].toSorted(comparePaths);
178986
179997
  }
178987
179998
  }
179999
+ function resolvedState(outcome) {
180000
+ if (!outcome.ok) {
180001
+ return { ...emptyState(), diagnostics: sortDiagnostics(outcome.diagnostics) };
180002
+ }
180003
+ const { codeFiles, docFiles, diagnostics: scanDiagnostics, contentByFile, graph } = outcome.scan;
180004
+ const relationship = resolveLinks({ codeFiles, docFiles, scanDiagnostics, audit: false });
180005
+ return {
180006
+ graph,
180007
+ index: buildPositionIndex(graph),
180008
+ diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
180009
+ contentByFile
180010
+ };
180011
+ }
178988
180012
  function emptyState() {
178989
180013
  const graph = buildLinkGraph([], []);
178990
180014
  return {
@@ -178995,6 +180019,74 @@ function emptyState() {
178995
180019
  };
178996
180020
  }
178997
180021
 
180022
+ // src/lsp/scheduler.ts
180023
+ class RescanScheduler {
180024
+ options;
180025
+ generation = 0;
180026
+ dirty = false;
180027
+ debounce;
180028
+ active;
180029
+ stopped = false;
180030
+ constructor(options) {
180031
+ this.options = options;
180032
+ }
180033
+ request(when) {
180034
+ if (this.stopped) {
180035
+ return;
180036
+ }
180037
+ this.generation += 1;
180038
+ this.dirty = true;
180039
+ this.active?.task.cancel();
180040
+ this.clearDebounce();
180041
+ if (when === "debounced") {
180042
+ const handle = this.options.timers.setTimeout(() => {
180043
+ this.debounce = undefined;
180044
+ this.startIfReady();
180045
+ }, this.options.debounceMs);
180046
+ this.debounce = { handle };
180047
+ return;
180048
+ }
180049
+ this.startIfReady();
180050
+ }
180051
+ stop() {
180052
+ this.stopped = true;
180053
+ this.clearDebounce();
180054
+ this.active?.task.cancel();
180055
+ }
180056
+ startIfReady() {
180057
+ if (this.stopped || !this.dirty || this.active !== undefined || this.debounce !== undefined) {
180058
+ return;
180059
+ }
180060
+ this.dirty = false;
180061
+ const active = { generation: this.generation, task: this.options.scan() };
180062
+ this.active = active;
180063
+ active.task.promise.then((state) => this.settle(active, () => this.accept(active, state)), (error) => this.settle(active, () => this.fail(error)));
180064
+ }
180065
+ settle(active, report) {
180066
+ if (this.active === active) {
180067
+ this.active = undefined;
180068
+ }
180069
+ report();
180070
+ this.startIfReady();
180071
+ }
180072
+ accept(active, state) {
180073
+ if (!this.stopped && active.generation === this.generation) {
180074
+ this.options.onState(state);
180075
+ }
180076
+ }
180077
+ fail(error) {
180078
+ if (!isAbortError(error)) {
180079
+ this.options.onError(error);
180080
+ }
180081
+ }
180082
+ clearDebounce() {
180083
+ if (this.debounce !== undefined) {
180084
+ this.options.timers.clearTimeout(this.debounce.handle);
180085
+ this.debounce = undefined;
180086
+ }
180087
+ }
180088
+ }
180089
+
178998
180090
  // src/lsp/transport.ts
178999
180091
  var HEADER_SEPARATOR = `\r
179000
180092
  \r
@@ -179062,6 +180154,10 @@ function parseContentLength(headerText) {
179062
180154
  }
179063
180155
 
179064
180156
  // src/lsp/server.ts
180157
+ var GLOBAL_TIMERS = {
180158
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
180159
+ clearTimeout: (handle) => clearTimeout(handle)
180160
+ };
179065
180161
  var CAPABILITIES = {
179066
180162
  textDocumentSync: 1,
179067
180163
  hoverProvider: true,
@@ -179072,44 +180168,55 @@ var CAPABILITIES = {
179072
180168
  class Server {
179073
180169
  send;
179074
180170
  project = null;
180171
+ scheduler = null;
179075
180172
  openFiles = new Set;
179076
- dirty = true;
179077
- timer = null;
179078
180173
  shuttingDown = false;
179079
180174
  makeProject;
179080
180175
  debounceMs;
179081
180176
  onExit;
180177
+ timers;
180178
+ onScanError;
179082
180179
  constructor(send, options = {}) {
179083
180180
  this.send = send;
179084
180181
  this.makeProject = options.makeProject ?? ((root) => new Project(root));
179085
180182
  this.debounceMs = options.debounceMs ?? 50;
179086
180183
  this.onExit = options.onExit ?? ((code) => process.exit(code));
180184
+ this.timers = options.timers ?? GLOBAL_TIMERS;
180185
+ this.onScanError = options.onScanError ?? ((error) => process.stderr.write(`docbridge lsp: scan failed: ${reasonOf(error)}
180186
+ `));
179087
180187
  }
179088
180188
  handle(message) {
179089
180189
  switch (message.method) {
179090
180190
  case "initialize":
179091
180191
  this.respond(message.id, { capabilities: CAPABILITIES });
179092
- this.project = this.makeProject(resolveRoot(message.params));
180192
+ this.start(this.makeProject(resolveRoot(message.params)));
179093
180193
  break;
179094
180194
  case "initialized":
179095
- this.flush();
180195
+ this.scheduler?.request("now");
179096
180196
  break;
179097
180197
  case "shutdown":
179098
180198
  this.shuttingDown = true;
179099
- this.clearTimer();
180199
+ this.scheduler?.stop();
179100
180200
  this.respond(message.id, null);
179101
180201
  break;
179102
180202
  case "exit":
180203
+ this.scheduler?.stop();
179103
180204
  this.onExit(this.shuttingDown ? 0 : 1);
179104
180205
  break;
179105
180206
  case "textDocument/didOpen":
179106
- this.onDidOpen(message.params);
180207
+ if (!this.shuttingDown) {
180208
+ this.onDidOpen(message.params);
180209
+ }
179107
180210
  break;
179108
180211
  case "textDocument/didChange":
179109
- this.onDidChange(message.params);
180212
+ if (!this.shuttingDown) {
180213
+ this.onDidChange(message.params);
180214
+ }
179110
180215
  break;
179111
180216
  case "textDocument/didClose":
179112
- this.onDidClose(message.params);
180217
+ if (!this.shuttingDown) {
180218
+ this.onDidClose(message.params);
180219
+ }
179113
180220
  break;
179114
180221
  case "textDocument/hover":
179115
180222
  this.respond(message.id, this.onHover(message.params));
@@ -179126,6 +180233,16 @@ class Server {
179126
180233
  }
179127
180234
  }
179128
180235
  }
180236
+ start(project) {
180237
+ this.project = project;
180238
+ this.scheduler = new RescanScheduler({
180239
+ scan: () => project.resolveAsync(),
180240
+ onState: (state) => this.publishOpenFiles(state),
180241
+ onError: this.onScanError,
180242
+ debounceMs: this.debounceMs,
180243
+ timers: this.timers
180244
+ });
180245
+ }
179129
180246
  onDidOpen(params) {
179130
180247
  const doc = textDocument(params);
179131
180248
  const rel = this.relPath(doc?.uri);
@@ -179135,8 +180252,7 @@ class Server {
179135
180252
  }
179136
180253
  this.openFiles.add(rel);
179137
180254
  this.project.setOverlay(rel, text);
179138
- this.dirty = true;
179139
- this.flush();
180255
+ this.scheduler?.request("now");
179140
180256
  }
179141
180257
  onDidChange(params) {
179142
180258
  const record = params;
@@ -179147,7 +180263,7 @@ class Server {
179147
180263
  return;
179148
180264
  }
179149
180265
  this.project.setOverlay(rel, text);
179150
- this.scheduleFlush();
180266
+ this.scheduler?.request("debounced");
179151
180267
  }
179152
180268
  onDidClose(params) {
179153
180269
  const doc = textDocument(params);
@@ -179157,9 +180273,8 @@ class Server {
179157
180273
  }
179158
180274
  this.openFiles.delete(rel);
179159
180275
  this.project.clearOverlay(rel);
179160
- this.dirty = true;
179161
180276
  this.publish(rel, []);
179162
- this.flush();
180277
+ this.scheduler?.request("now");
179163
180278
  }
179164
180279
  onHover(params) {
179165
180280
  const located = this.locate(params);
@@ -179198,7 +180313,6 @@ class Server {
179198
180313
  if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
179199
180314
  return null;
179200
180315
  }
179201
- this.flush();
179202
180316
  return {
179203
180317
  state: this.project.state,
179204
180318
  rel,
@@ -179206,28 +180320,14 @@ class Server {
179206
180320
  position: fromLspPosition({ line: position.line, character: position.character })
179207
180321
  };
179208
180322
  }
179209
- scheduleFlush() {
179210
- this.dirty = true;
179211
- this.clearTimer();
179212
- this.timer = setTimeout(() => {
179213
- this.timer = null;
179214
- this.flush();
179215
- }, this.debounceMs);
179216
- }
179217
- flush() {
179218
- this.clearTimer();
179219
- if (this.project === null || !this.dirty) {
179220
- return;
179221
- }
179222
- this.dirty = false;
179223
- const state = this.project.resolve();
180323
+ publishOpenFiles(state) {
179224
180324
  for (const rel of this.openFiles) {
179225
180325
  const content = state.contentByFile.get(rel) ?? "";
179226
180326
  this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
179227
180327
  }
179228
180328
  }
179229
180329
  publish(rel, diagnostics) {
179230
- if (this.project === null) {
180330
+ if (this.project === null || this.shuttingDown) {
179231
180331
  return;
179232
180332
  }
179233
180333
  this.send({
@@ -179248,12 +180348,6 @@ class Server {
179248
180348
  }
179249
180349
  this.send({ jsonrpc: "2.0", id, result });
179250
180350
  }
179251
- clearTimer() {
179252
- if (this.timer !== null) {
179253
- clearTimeout(this.timer);
179254
- this.timer = null;
179255
- }
179256
- }
179257
180351
  }
179258
180352
  function resolveRoot(params) {
179259
180353
  const record = params;
@@ -179347,7 +180441,7 @@ function check(options) {
179347
180441
  }
179348
180442
 
179349
180443
  // src/query/related.ts
179350
- import { isAbsolute as isAbsolute2, relative as relative2 } from "node:path";
180444
+ import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
179351
180445
  function normalizeChangedPaths(projectRoot, paths) {
179352
180446
  const normalized = new Set;
179353
180447
  for (const raw of paths) {
@@ -179355,7 +180449,7 @@ function normalizeChangedPaths(projectRoot, paths) {
179355
180449
  if (trimmed === "") {
179356
180450
  continue;
179357
180451
  }
179358
- const relativized = isAbsolute2(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
180452
+ const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
179359
180453
  normalized.add(relativized.replace(/^(\.\/)+/, ""));
179360
180454
  }
179361
180455
  return [...normalized];
@@ -179777,7 +180871,7 @@ function comparePairs(left, right) {
179777
180871
  // src/setup/update-cache.ts
179778
180872
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
179779
180873
  import { homedir } from "node:os";
179780
- import { dirname as dirname3, join as join7 } from "node:path";
180874
+ import { dirname as dirname3, join as join8 } from "node:path";
179781
180875
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
179782
180876
  var UPDATE_CACHE_SCHEMA_VERSION = 1;
179783
180877
  function resolveUpdateCachePath(env = process.env, home = homedir()) {
@@ -179785,8 +180879,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
179785
180879
  if (explicit !== undefined && explicit.length > 0) {
179786
180880
  return explicit;
179787
180881
  }
179788
- const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join7(home, ".cache");
179789
- return join7(cacheRoot, "docbridge", "update-check.json");
180882
+ const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join8(home, ".cache");
180883
+ return join8(cacheRoot, "docbridge", "update-check.json");
179790
180884
  }
179791
180885
  function readUpdateCache(path) {
179792
180886
  let rawText;
@@ -180030,7 +181124,7 @@ function isEnabledFlag(value) {
180030
181124
 
180031
181125
  // src/cli/docs.ts
180032
181126
  import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
180033
- import { join as join8 } from "node:path";
181127
+ import { join as join9 } from "node:path";
180034
181128
 
180035
181129
  // src/cli/errors.ts
180036
181130
  class CliError extends Error {
@@ -180201,7 +181295,7 @@ function parseDocument(name, source) {
180201
181295
  };
180202
181296
  }
180203
181297
  function createFileDocumentationReader(packageRoot) {
180204
- const docsRoot = join8(packageRoot, "docs", "user");
181298
+ const docsRoot = join9(packageRoot, "docs", "user");
180205
181299
  function documents() {
180206
181300
  let fileNames;
180207
181301
  try {
@@ -180214,7 +181308,7 @@ function createFileDocumentationReader(packageRoot) {
180214
181308
  }
180215
181309
  return fileNames.map((fileName) => {
180216
181310
  const name = fileName.slice(0, -".md".length);
180217
- return parseDocument(name, readFileSync5(join8(docsRoot, fileName), "utf8"));
181311
+ return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
180218
181312
  });
180219
181313
  }
180220
181314
  return {
@@ -180611,12 +181705,12 @@ function hasHelpFlag(args) {
180611
181705
  }
180612
181706
 
180613
181707
  // src/cli/init.ts
180614
- import { readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
180615
- import { join as join12, resolve as resolve2 } from "node:path";
181708
+ import { readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
181709
+ import { join as join13, resolve as resolve3 } from "node:path";
180616
181710
 
180617
181711
  // src/setup/init-discovery.ts
180618
- import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
180619
- import { basename, dirname as dirname4, join as join9 } from "node:path";
181712
+ import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
181713
+ import { basename, dirname as dirname4, join as join10 } from "node:path";
180620
181714
  var HIGH_CONFIDENCE_DIR_NAMES = new Set([
180621
181715
  "docs/specs",
180622
181716
  "specs",
@@ -180646,22 +181740,42 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
180646
181740
  "contributing"
180647
181741
  ]);
180648
181742
  var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
180649
- var TYPESCRIPT_PATTERNS = [
180650
- "src/**/*.ts",
180651
- "lib/**/*.ts",
180652
- "packages/*/src/**/*.ts",
180653
- "apps/*/src/**/*.ts"
180654
- ];
181743
+ var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
181744
+ var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
181745
+ var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
180655
181746
  var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
180656
181747
  var DART_PATTERNS = ["lib/**/*.dart"];
180657
181748
  var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
180658
181749
  var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
181750
+ var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
181751
+ var JAVA_PATTERNS = ["src/main/java/**/*.java", "src/**/*.java"];
180659
181752
  var LANGUAGE_PATTERNS = {
180660
181753
  typescript: TYPESCRIPT_PATTERNS,
180661
181754
  swift: SWIFT_PATTERNS,
180662
181755
  dart: DART_PATTERNS,
180663
181756
  rust: RUST_PATTERNS,
180664
- go: GO_PATTERNS
181757
+ go: GO_PATTERNS,
181758
+ javascript: JAVASCRIPT_PATTERNS,
181759
+ ruby: RUBY_PATTERNS,
181760
+ java: JAVA_PATTERNS
181761
+ };
181762
+ var EXCLUDED_CODE_SEGMENTS = {
181763
+ typescript: ["__tests__", "tests", "test"],
181764
+ swift: ["Tests", "tests"],
181765
+ dart: ["test"],
181766
+ rust: ["target", "tests", "benches", "examples"],
181767
+ go: ["vendor", "testdata"],
181768
+ javascript: ["__tests__", "tests", "test"],
181769
+ python: ["tests", "venv", "dist", "site-packages"],
181770
+ ruby: ["spec", "test", "vendor"],
181771
+ java: ["target", "src/test"]
181772
+ };
181773
+ var TEST_FILE_PATTERNS = {
181774
+ typescript: /\.(?:test|spec)\.[cm]?tsx?$/u,
181775
+ swift: /tests\.swift$/u,
181776
+ dart: /_test\.dart$/u,
181777
+ go: /_test\.go$/u,
181778
+ javascript: /\.(?:test|spec)\.[cm]?jsx?$/u
180665
181779
  };
180666
181780
  function discoverRepository(projectRoot) {
180667
181781
  return {
@@ -180745,8 +181859,8 @@ function discoverCodeScope(projectRoot) {
180745
181859
  };
180746
181860
  }
180747
181861
  function discoverAgentTarget(projectRoot) {
180748
- const hasAgentsDir = existsSync3(join9(projectRoot, ".agents"));
180749
- const hasClaudeDir = existsSync3(join9(projectRoot, ".claude"));
181862
+ const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
181863
+ const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
180750
181864
  let defaultTarget = "none";
180751
181865
  let recommendedTarget = "none";
180752
181866
  let message;
@@ -180773,7 +181887,7 @@ function discoverAgentTarget(projectRoot) {
180773
181887
  };
180774
181888
  }
180775
181889
  function collectMarkdownFiles(projectRoot, currentDir = ".") {
180776
- const absoluteDir = join9(projectRoot, currentDir);
181890
+ const absoluteDir = join10(projectRoot, currentDir);
180777
181891
  let entries;
180778
181892
  try {
180779
181893
  entries = readdirSync3(absoluteDir);
@@ -180787,10 +181901,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
180787
181901
  if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
180788
181902
  continue;
180789
181903
  }
180790
- const absolutePath = join9(projectRoot, relPath);
181904
+ const absolutePath = join10(projectRoot, relPath);
180791
181905
  let stats;
180792
181906
  try {
180793
- stats = statSync2(absolutePath);
181907
+ stats = statSync3(absolutePath);
180794
181908
  } catch {
180795
181909
  continue;
180796
181910
  }
@@ -180828,8 +181942,23 @@ function scoreDocsDirectory(directory) {
180828
181942
  }
180829
181943
  return score;
180830
181944
  }
181945
+ function scriptPatterns(language) {
181946
+ return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
181947
+ }
180831
181948
  function activeCodePatterns(projectRoot, language) {
180832
- return LANGUAGE_PATTERNS[language].filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
181949
+ const candidates = language === "python" ? pythonPatterns(projectRoot) : LANGUAGE_PATTERNS[language];
181950
+ const active = candidates.filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
181951
+ return language === "java" ? active.slice(0, 1) : active;
181952
+ }
181953
+ function pythonPatterns(projectRoot) {
181954
+ let entries;
181955
+ try {
181956
+ entries = readdirSync3(projectRoot, { withFileTypes: true });
181957
+ } catch {
181958
+ return [];
181959
+ }
181960
+ const packages = entries.filter((entry) => entry.isDirectory() && entry.name !== "src" && !entry.name.startsWith(".") && !IGNORED_WALK_SEGMENTS.has(entry.name) && existsSync4(join10(projectRoot, entry.name, "__init__.py"))).map((entry) => entry.name).toSorted();
181961
+ return ["src/**/*.py", ...packages.map((name) => `${name}/**/*.py`)];
180833
181962
  }
180834
181963
  function countCodeFiles(projectRoot, patterns, language) {
180835
181964
  const seen = new Set;
@@ -180845,33 +181974,9 @@ function countCodeFiles(projectRoot, patterns, language) {
180845
181974
  function isExcludedCodeFile(filePath, language) {
180846
181975
  const lower = filePath.toLowerCase();
180847
181976
  const segments = filePath.split("/");
180848
- if (language === "typescript") {
180849
- if (lower.endsWith(".d.ts")) {
180850
- return true;
180851
- }
180852
- if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
180853
- return true;
180854
- }
180855
- }
180856
- if (language === "swift") {
180857
- if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
180858
- return true;
180859
- }
180860
- }
180861
- if (language === "dart") {
180862
- if (lower.endsWith("_test.dart") || segments.includes("test")) {
180863
- return true;
180864
- }
180865
- }
180866
- if (language === "rust") {
180867
- if (segments.includes("target") || segments.includes("tests") || segments.includes("benches") || segments.includes("examples")) {
180868
- return true;
180869
- }
180870
- }
180871
- if (language === "go") {
180872
- if (lower.endsWith("_test.go") || segments.includes("vendor") || segments.includes("testdata")) {
180873
- return true;
180874
- }
181977
+ const delimited = `/${filePath}/`;
181978
+ if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((run) => delimited.includes(`/${run}/`))) {
181979
+ return true;
180875
181980
  }
180876
181981
  if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
180877
181982
  return true;
@@ -180880,17 +181985,17 @@ function isExcludedCodeFile(filePath, language) {
180880
181985
  }
180881
181986
 
180882
181987
  // src/setup/init-plan.ts
180883
- import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
180884
- import { join as join11, relative as relative3 } from "node:path";
181988
+ import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
181989
+ import { join as join12, relative as relative3 } from "node:path";
180885
181990
 
180886
181991
  // src/setup/skill-assets.ts
180887
181992
  import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
180888
- import { join as join10 } from "node:path";
181993
+ import { join as join11 } from "node:path";
180889
181994
  function classifyManagedPath(projectRoot, relativePath) {
180890
181995
  const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
180891
181996
  let current = projectRoot;
180892
181997
  for (const [index, segment] of segments.entries()) {
180893
- current = join10(current, segment);
181998
+ current = join11(current, segment);
180894
181999
  const isLast = index === segments.length - 1;
180895
182000
  let stats;
180896
182001
  try {
@@ -180937,7 +182042,7 @@ function compareSkillTree(installedDir, templateDir) {
180937
182042
  continue;
180938
182043
  }
180939
182044
  installedFiles.delete(file);
180940
- if (readFileOrUndefined(join10(installedDir, file)) !== readFileOrUndefined(join10(templateDir, file))) {
182045
+ if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
180941
182046
  modified.push(file);
180942
182047
  }
180943
182048
  }
@@ -180948,7 +182053,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180948
182053
  return;
180949
182054
  }
180950
182055
  const kind = classifyManagedPath(projectRoot, operation.path);
180951
- const destinationDir = join10(projectRoot, operation.path);
182056
+ const destinationDir = join11(projectRoot, operation.path);
180952
182057
  if (operation.action === "remove") {
180953
182058
  if (kind === "directory") {
180954
182059
  rmSync(destinationDir, { recursive: true, force: true });
@@ -180966,7 +182071,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180966
182071
  rmSync(destinationDir, { recursive: true, force: true });
180967
182072
  }
180968
182073
  mkdirSync3(destinationDir, { recursive: true });
180969
- cpSync(join10(packageRoot, "templates", "skills", skillName), destinationDir, {
182074
+ cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
180970
182075
  recursive: true,
180971
182076
  force: true
180972
182077
  });
@@ -180974,7 +182079,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180974
182079
  function collectSkillFiles(root, prefix, files) {
180975
182080
  let entries;
180976
182081
  try {
180977
- entries = readdirSync4(join10(root, prefix), { withFileTypes: true });
182082
+ entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
180978
182083
  } catch {
180979
182084
  return;
180980
182085
  }
@@ -181085,14 +182190,14 @@ function buildConfigFromScope(scope) {
181085
182190
  };
181086
182191
  }
181087
182192
  function listDistributableSkills(packageRoot) {
181088
- const skillsRoot = join11(packageRoot, "templates", "skills");
182193
+ const skillsRoot = join12(packageRoot, "templates", "skills");
181089
182194
  let entries;
181090
182195
  try {
181091
182196
  entries = readdirSync5(skillsRoot);
181092
182197
  } catch {
181093
182198
  return [];
181094
182199
  }
181095
- return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync4(join11(skillsRoot, entry, "SKILL.md"))).toSorted();
182200
+ return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync5(join12(skillsRoot, entry, "SKILL.md"))).toSorted();
181096
182201
  }
181097
182202
  function formatInitPlan(plan) {
181098
182203
  const lines = [];
@@ -181197,7 +182302,7 @@ function planSkillOperations(input) {
181197
182302
  if (!availableSkills.has(skillName)) {
181198
182303
  continue;
181199
182304
  }
181200
- const destinationDir = join11(input.projectRoot, destination, skillName);
182305
+ const destinationDir = join12(input.projectRoot, destination, skillName);
181201
182306
  const path = relative3(input.projectRoot, destinationDir);
181202
182307
  const kind = classifyManagedPath(input.projectRoot, path);
181203
182308
  if (kind !== "absent" && kind !== "directory") {
@@ -181236,7 +182341,7 @@ function planLegacySkillOperations(input) {
181236
182341
  const operations = [];
181237
182342
  const leftover = [];
181238
182343
  for (const skillName of LEGACY_SKILL_NAMES) {
181239
- const destinationDir = join11(input.projectRoot, input.destination, skillName);
182344
+ const destinationDir = join12(input.projectRoot, input.destination, skillName);
181240
182345
  const path = relative3(input.projectRoot, destinationDir);
181241
182346
  const kind = classifyManagedPath(input.projectRoot, path);
181242
182347
  if (kind === "absent") {
@@ -181316,7 +182421,7 @@ function resolveInitWithAgentTarget(discovery, options) {
181316
182421
  return { target: discovery.agent.defaultTarget, error: undefined };
181317
182422
  }
181318
182423
  function readExistingConfig(projectRoot) {
181319
- const configPath = join11(projectRoot, CONFIG_FILE_NAME2);
182424
+ const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
181320
182425
  let rawText;
181321
182426
  try {
181322
182427
  rawText = readFileSync7(configPath, "utf8");
@@ -181567,7 +182672,7 @@ function executePlan(projectRoot, plan, packageRoot) {
181567
182672
  }
181568
182673
  }
181569
182674
  function executeFileOperation(projectRoot, operation) {
181570
- const absolutePath = join12(projectRoot, operation.path);
182675
+ const absolutePath = join13(projectRoot, operation.path);
181571
182676
  if (operation.action === "create" && operation.content !== undefined) {
181572
182677
  writeFileSync2(absolutePath, operation.content, "utf8");
181573
182678
  }
@@ -181576,10 +182681,10 @@ function executeFileOperation(projectRoot, operation) {
181576
182681
  }
181577
182682
  }
181578
182683
  function resolveProjectRoot(root, command) {
181579
- const projectRoot = resolve2(root);
182684
+ const projectRoot = resolve3(root);
181580
182685
  let stats;
181581
182686
  try {
181582
- stats = statSync3(projectRoot);
182687
+ stats = statSync4(projectRoot);
181583
182688
  } catch {
181584
182689
  throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
181585
182690
  }
@@ -181614,24 +182719,12 @@ ${block.content}`;
181614
182719
  const fence = codeFence(block.content);
181615
182720
  return `${header}
181616
182721
 
181617
- ${fence}${fenceLanguage(block.language)}
182722
+ ${fence}${fenceLanguage(block)}
181618
182723
  ${block.content}
181619
182724
  ${fence}`;
181620
182725
  }
181621
- function fenceLanguage(language) {
181622
- if (language === "swift") {
181623
- return "swift";
181624
- }
181625
- if (language === "dart") {
181626
- return "dart";
181627
- }
181628
- if (language === "rust") {
181629
- return "rust";
181630
- }
181631
- if (language === "go") {
181632
- return "go";
181633
- }
181634
- return "ts";
182726
+ function fenceLanguage(block) {
182727
+ return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
181635
182728
  }
181636
182729
  function codeFence(content) {
181637
182730
  let longestRun = 0;
@@ -181763,12 +182856,12 @@ function formatRelatedSummary(summary) {
181763
182856
  }
181764
182857
 
181765
182858
  // src/cli/upgrade.ts
181766
- import { statSync as statSync4 } from "node:fs";
181767
- import { resolve as resolve3 } from "node:path";
182859
+ import { statSync as statSync5 } from "node:fs";
182860
+ import { resolve as resolve4 } from "node:path";
181768
182861
 
181769
182862
  // src/setup/upgrade-plan.ts
181770
- import { existsSync as existsSync5 } from "node:fs";
181771
- import { join as join13, relative as relative4 } from "node:path";
182863
+ import { existsSync as existsSync6 } from "node:fs";
182864
+ import { join as join14, relative as relative4 } from "node:path";
181772
182865
  var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
181773
182866
  function reportCliVersion(currentVersion, latest) {
181774
182867
  if (latest.status !== "ok") {
@@ -181786,7 +182879,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
181786
182879
  return explicitTarget ?? discovery.agent.defaultTarget;
181787
182880
  }
181788
182881
  function inspectManagedSkill(input) {
181789
- const destinationDir = join13(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
182882
+ const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
181790
182883
  const path = toRootRelative(input.projectRoot, destinationDir);
181791
182884
  const empty = {
181792
182885
  destination: input.destination,
@@ -181802,7 +182895,7 @@ function inspectManagedSkill(input) {
181802
182895
  if (!input.templateAvailable) {
181803
182896
  return { ...empty, state: "template-missing" };
181804
182897
  }
181805
- const comparison = compareSkillTree(destinationDir, join13(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
182898
+ const comparison = compareSkillTree(destinationDir, join14(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
181806
182899
  const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
181807
182900
  return {
181808
182901
  destination: input.destination,
@@ -181816,13 +182909,13 @@ function inspectManagedSkill(input) {
181816
182909
  function inspectLegacySkills(projectRoot, destination) {
181817
182910
  const reports = [];
181818
182911
  for (const skillName of LEGACY_SKILL_NAMES) {
181819
- const destinationDir = join13(projectRoot, destination, skillName);
182912
+ const destinationDir = join14(projectRoot, destination, skillName);
181820
182913
  const path = toRootRelative(projectRoot, destinationDir);
181821
182914
  const kind = classifyManagedPath(projectRoot, path);
181822
182915
  if (kind === "absent") {
181823
182916
  continue;
181824
182917
  }
181825
- if (kind === "symlinked-parent" && !existsSync5(destinationDir)) {
182918
+ if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
181826
182919
  continue;
181827
182920
  }
181828
182921
  reports.push({ path, kind });
@@ -182086,10 +183179,10 @@ function parseAgentTarget(value) {
182086
183179
  return value;
182087
183180
  }
182088
183181
  function resolveProjectRoot2(root) {
182089
- const projectRoot = resolve3(root);
183182
+ const projectRoot = resolve4(root);
182090
183183
  let stats;
182091
183184
  try {
182092
- stats = statSync4(projectRoot);
183185
+ stats = statSync5(projectRoot);
182093
183186
  } catch {
182094
183187
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
182095
183188
  }
@@ -182114,10 +183207,10 @@ function parseGraphOptions(args) {
182114
183207
  return parseCommandOptions("graph", args);
182115
183208
  }
182116
183209
  function resolveProjectRoot3(root, command) {
182117
- const projectRoot = resolve4(root);
183210
+ const projectRoot = resolve5(root);
182118
183211
  let stats;
182119
183212
  try {
182120
- stats = statSync5(projectRoot);
183213
+ stats = statSync6(projectRoot);
182121
183214
  } catch {
182122
183215
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182123
183216
  }
@@ -182241,7 +183334,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
182241
183334
  if (invalid.length === 0) {
182242
183335
  return;
182243
183336
  }
182244
- return existsSync6(join14(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
183337
+ return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
182245
183338
  }
182246
183339
  function unknownCommandGuidance(command) {
182247
183340
  const suggestion = nearestMatch(command, SUBCOMMANDS);
@@ -182316,7 +183409,7 @@ function run(argv, io = {
182316
183409
  function rootOptionOf(argv, currentDirectory) {
182317
183410
  const flagIndex = argv.indexOf("--root");
182318
183411
  const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
182319
- return value === undefined ? currentDirectory : resolve4(currentDirectory, value);
183412
+ return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
182320
183413
  }
182321
183414
  function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
182322
183415
  const latest = cliRuntime.latest;