docbridge 0.10.1 → 0.12.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 (44) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +16 -9
  3. package/dist/bin/darwin-arm64/SHA256SUMS +3 -2
  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 +3 -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/bin/linux-x64/docbridge_dart_scanner +0 -0
  11. package/dist/index.js +1773 -668
  12. package/dist/workers/java/docbridge-java-scanner.jar +0 -0
  13. package/dist/workers/python/docbridge_python_scanner/__init__.py +6 -0
  14. package/dist/workers/python/docbridge_python_scanner/comments.py +177 -0
  15. package/dist/workers/python/docbridge_python_scanner/declarations.py +318 -0
  16. package/dist/workers/python/docbridge_python_scanner/links.py +62 -0
  17. package/dist/workers/python/docbridge_python_scanner/positions.py +75 -0
  18. package/dist/workers/python/docbridge_python_scanner/protocol.py +105 -0
  19. package/dist/workers/python/docbridge_python_scanner/scanner.py +214 -0
  20. package/dist/workers/python/docbridge_python_scanner.py +16 -0
  21. package/dist/workers/ruby/bin/docbridge-ruby-scanner +10 -0
  22. package/dist/workers/ruby/lib/docbridge_ruby_scanner/cli.rb +30 -0
  23. package/dist/workers/ruby/lib/docbridge_ruby_scanner/comments.rb +55 -0
  24. package/dist/workers/ruby/lib/docbridge_ruby_scanner/compat.rb +39 -0
  25. package/dist/workers/ruby/lib/docbridge_ruby_scanner/declarations.rb +230 -0
  26. package/dist/workers/ruby/lib/docbridge_ruby_scanner/links.rb +38 -0
  27. package/dist/workers/ruby/lib/docbridge_ruby_scanner/positions.rb +50 -0
  28. package/dist/workers/ruby/lib/docbridge_ruby_scanner/probe.rb +28 -0
  29. package/dist/workers/ruby/lib/docbridge_ruby_scanner/protocol.rb +161 -0
  30. package/dist/workers/ruby/lib/docbridge_ruby_scanner.rb +21 -0
  31. package/docs/README.md +10 -3
  32. package/docs/user/automation.md +8 -4
  33. package/docs/user/commands.md +11 -3
  34. package/docs/user/configuration.md +105 -13
  35. package/docs/user/getting-started.md +1 -1
  36. package/docs/user/linking.md +105 -10
  37. package/docs/user/troubleshooting.md +23 -10
  38. package/package.json +1 -1
  39. package/schemas/common-output.schema.json +11 -1
  40. package/schemas/context-output.schema.json +1 -1
  41. package/schemas/docbridge.schema.json +118 -2
  42. package/schemas/graph-output.schema.json +1 -1
  43. package/schemas/scanner-worker.schema.json +1 -1
  44. 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.10.1",
175584
+ version: "0.12.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 = [];
@@ -175787,11 +175777,47 @@ var KNOWN_CODE_LANGUAGES = [
175787
175777
  "typescript",
175788
175778
  "swift",
175789
175779
  "dart",
175790
- "rust"
175780
+ "rust",
175781
+ "go",
175782
+ "javascript",
175783
+ "python",
175784
+ "ruby"
175791
175785
  ];
175792
175786
  function isCodeLanguage(value) {
175793
175787
  return KNOWN_CODE_LANGUAGES.includes(value);
175794
175788
  }
175789
+ var LANGUAGE_SUFFIXES = {
175790
+ typescript: [".ts", ".tsx", ".mts", ".cts"],
175791
+ swift: [".swift"],
175792
+ dart: [".dart"],
175793
+ rust: [".rs"],
175794
+ go: [".go"],
175795
+ javascript: [".js", ".jsx", ".mjs", ".cjs"],
175796
+ python: [".py"],
175797
+ ruby: [".rb"]
175798
+ };
175799
+ var EXCLUDED_SUFFIXES = {
175800
+ typescript: [".d.ts", ".d.mts", ".d.cts"],
175801
+ swift: [],
175802
+ dart: [],
175803
+ rust: [],
175804
+ go: [],
175805
+ javascript: [],
175806
+ python: [],
175807
+ ruby: []
175808
+ };
175809
+ function codeFenceLanguage(language, filePath) {
175810
+ if (language === "typescript") {
175811
+ return filePath.endsWith(".tsx") ? "tsx" : "ts";
175812
+ }
175813
+ if (language === "javascript") {
175814
+ return filePath.endsWith(".jsx") ? "jsx" : "js";
175815
+ }
175816
+ return language;
175817
+ }
175818
+ function hasExcludedSuffix(language, relPath) {
175819
+ return EXCLUDED_SUFFIXES[language].some((suffix) => relPath.endsWith(suffix));
175820
+ }
175795
175821
  function collectCodeFiles(projectRoot, codeInclude) {
175796
175822
  const collected = [];
175797
175823
  const seen = new Set;
@@ -175800,7 +175826,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
175800
175826
  if (entry === undefined) {
175801
175827
  continue;
175802
175828
  }
175803
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175829
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175804
175830
  if (seen.has(relPath)) {
175805
175831
  continue;
175806
175832
  }
@@ -175818,7 +175844,7 @@ function codeFileOwners(projectRoot, codeInclude) {
175818
175844
  if (entry === undefined) {
175819
175845
  continue;
175820
175846
  }
175821
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175847
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175822
175848
  const existing = owners.get(relPath);
175823
175849
  if (existing === undefined) {
175824
175850
  owners.set(relPath, [language]);
@@ -175829,26 +175855,76 @@ function codeFileOwners(projectRoot, codeInclude) {
175829
175855
  }
175830
175856
  return owners;
175831
175857
  }
175858
+ function collectLanguageFiles(projectRoot, language, patterns) {
175859
+ return collectFiles(projectRoot, patterns).filter((relPath) => !hasExcludedSuffix(language, relPath));
175860
+ }
175861
+
175862
+ // src/config/scanner-runtimes.ts
175863
+ var RUNTIME_WORKER_LANGUAGES = ["python", "ruby", "java"];
175864
+ var KNOWN_RUNTIME_ENTRY_KEYS = new Set(["command"]);
175865
+ function validateScannerRuntimes(value, diagnostics) {
175866
+ if (!isPlainObject(value)) {
175867
+ diagnostics.push(configDiagnostic("config_invalid_value", "scanners", "`scanners` must be an object keyed by runtime-backed language."));
175868
+ return {};
175869
+ }
175870
+ const result = {};
175871
+ for (const [key, entry] of Object.entries(value)) {
175872
+ if (!isRuntimeWorkerLanguage(key)) {
175873
+ diagnostics.push(configDiagnostic("config_unknown_key", `scanners.${key}`, `Unknown scanner runtime: ${key}. Supported runtimes: ${RUNTIME_WORKER_LANGUAGES.join(", ")}.`));
175874
+ continue;
175875
+ }
175876
+ const command = validateRuntimeEntry(key, entry, diagnostics);
175877
+ if (command !== undefined) {
175878
+ result[key] = { command };
175879
+ }
175880
+ }
175881
+ return result;
175882
+ }
175883
+ function validateRuntimeEntry(language, value, diagnostics) {
175884
+ const target = `scanners.${language}`;
175885
+ if (!isPlainObject(value)) {
175886
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`command\` array.`));
175887
+ return;
175888
+ }
175889
+ for (const key of Object.keys(value)) {
175890
+ if (!KNOWN_RUNTIME_ENTRY_KEYS.has(key)) {
175891
+ diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
175892
+ }
175893
+ }
175894
+ const command = value.command;
175895
+ if (!Array.isArray(command) || command.length === 0 || !command.every((part) => typeof part === "string" && part !== "")) {
175896
+ 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.`));
175897
+ return;
175898
+ }
175899
+ return command;
175900
+ }
175901
+ function isRuntimeWorkerLanguage(value) {
175902
+ return RUNTIME_WORKER_LANGUAGES.includes(value);
175903
+ }
175904
+ function configDiagnostic(code, target, message) {
175905
+ return { severity: "error", code, target, message };
175906
+ }
175907
+ function isPlainObject(value) {
175908
+ return typeof value === "object" && value !== null && !Array.isArray(value);
175909
+ }
175832
175910
 
175833
175911
  // src/config/config.ts
175834
175912
  var CONFIG_FILE_NAME = "docbridge.config.json";
175835
175913
  var EMPTY_CONFIG = {
175836
175914
  include: { code: {}, docs: [] }
175837
175915
  };
175838
- var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
175916
+ var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
175839
175917
  var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
175840
175918
  var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
175841
- var LANGUAGE_SUFFIX = {
175842
- typescript: ".ts",
175843
- swift: ".swift",
175844
- dart: ".dart",
175845
- rust: ".rs"
175846
- };
175847
175919
  var LANGUAGE_VISIBILITY = {
175848
175920
  typescript: ["public", "protected", "private"],
175849
175921
  swift: ["public", "open", "internal"],
175850
175922
  dart: ["public"],
175851
- rust: ["pub", "private"]
175923
+ rust: ["pub", "private"],
175924
+ go: ["exported", "unexported"],
175925
+ javascript: ["public", "protected", "private"],
175926
+ python: ["public", "private"],
175927
+ ruby: ["public", "protected", "private"]
175852
175928
  };
175853
175929
  function loadConfig(projectRoot) {
175854
175930
  let rawText;
@@ -175877,7 +175953,7 @@ function resolveConfig(rawText) {
175877
175953
  config: EMPTY_CONFIG,
175878
175954
  ok: false,
175879
175955
  diagnostics: [
175880
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
175956
+ configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
175881
175957
  ]
175882
175958
  };
175883
175959
  }
@@ -175890,57 +175966,61 @@ function resolveConfig(rawText) {
175890
175966
  config: EMPTY_CONFIG,
175891
175967
  ok: false,
175892
175968
  diagnostics: [
175893
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
175969
+ configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
175894
175970
  ]
175895
175971
  };
175896
175972
  }
175897
175973
  const diagnostics = [];
175898
- if (!isPlainObject(parsed)) {
175899
- diagnostics.push(configDiagnostic("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
175974
+ if (!isPlainObject2(parsed)) {
175975
+ diagnostics.push(configDiagnostic2("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
175900
175976
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175901
175977
  }
175902
175978
  for (const key of Object.keys(parsed)) {
175903
175979
  if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
175904
- diagnostics.push(configDiagnostic("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
175980
+ diagnostics.push(configDiagnostic2("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
175905
175981
  }
175906
175982
  }
175907
175983
  if ("$schema" in parsed && typeof parsed.$schema !== "string") {
175908
- diagnostics.push(configDiagnostic("config_invalid_value", "$schema", "`$schema` must be a string."));
175984
+ diagnostics.push(configDiagnostic2("config_invalid_value", "$schema", "`$schema` must be a string."));
175909
175985
  }
175986
+ const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
175910
175987
  const include = parsed.include;
175911
- if (!isPlainObject(include)) {
175912
- diagnostics.push(configDiagnostic("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175988
+ if (!isPlainObject2(include)) {
175989
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175913
175990
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175914
175991
  }
175915
175992
  for (const key of Object.keys(include)) {
175916
175993
  if (!KNOWN_INCLUDE_KEYS.has(key)) {
175917
- diagnostics.push(configDiagnostic("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
175994
+ diagnostics.push(configDiagnostic2("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
175918
175995
  }
175919
175996
  }
175920
175997
  const code = validateCodeInclude(include.code, diagnostics);
175921
- validatePatternArray(include.docs, "docs", ".md", false, diagnostics);
175998
+ validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
175922
175999
  const ok = diagnostics.length === 0;
175923
- const config = ok ? { include: { code, docs: include.docs } } : EMPTY_CONFIG;
176000
+ const config = ok ? {
176001
+ include: { code, docs: include.docs },
176002
+ ...scanners === undefined ? {} : { scanners }
176003
+ } : EMPTY_CONFIG;
175924
176004
  return { config, diagnostics, ok };
175925
176005
  }
175926
176006
  function validateCodeInclude(value, diagnostics) {
175927
176007
  if (Array.isArray(value)) {
175928
- 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.'));
176008
+ 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.'));
175929
176009
  return {};
175930
176010
  }
175931
- if (!isPlainObject(value)) {
175932
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
176011
+ if (!isPlainObject2(value)) {
176012
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
175933
176013
  return {};
175934
176014
  }
175935
176015
  const keys = Object.keys(value);
175936
176016
  if (keys.length === 0) {
175937
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
176017
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
175938
176018
  return {};
175939
176019
  }
175940
176020
  const result = {};
175941
176021
  for (const key of keys) {
175942
176022
  if (!isCodeLanguage(key)) {
175943
- diagnostics.push(configDiagnostic("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
176023
+ diagnostics.push(configDiagnostic2("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
175944
176024
  continue;
175945
176025
  }
175946
176026
  const entry = validateCodeEntry(key, value[key], diagnostics);
@@ -175952,20 +176032,20 @@ function validateCodeInclude(value, diagnostics) {
175952
176032
  }
175953
176033
  function validateCodeEntry(language, value, diagnostics) {
175954
176034
  const target = `include.code.${language}`;
175955
- if (!isPlainObject(value)) {
175956
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
176035
+ if (!isPlainObject2(value)) {
176036
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
175957
176037
  return;
175958
176038
  }
175959
176039
  for (const key of Object.keys(value)) {
175960
176040
  if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
175961
- diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
176041
+ diagnostics.push(configDiagnostic2("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
175962
176042
  }
175963
176043
  }
175964
176044
  const before = diagnostics.length;
175965
- validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIX[language], language === "typescript", diagnostics);
176045
+ validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIXES[language], EXCLUDED_SUFFIXES[language], diagnostics);
175966
176046
  if ("visibility" in value) {
175967
176047
  if (!isStringArray(value.visibility)) {
175968
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
176048
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
175969
176049
  } else {
175970
176050
  validateVisibilityOptions(language, value.visibility, target, diagnostics);
175971
176051
  }
@@ -175982,47 +176062,49 @@ function validateCodeEntry(language, value, diagnostics) {
175982
176062
  function validateVisibilityOptions(language, values, target, diagnostics) {
175983
176063
  const allowed = LANGUAGE_VISIBILITY[language];
175984
176064
  if (allowed.length === 0) {
175985
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
176065
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
175986
176066
  return;
175987
176067
  }
175988
176068
  for (const value of values) {
175989
176069
  if (!allowed.includes(value)) {
175990
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
176070
+ diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
175991
176071
  }
175992
176072
  }
175993
176073
  }
175994
- function validatePatternArray(value, field, requiredSuffix, excludeDeclarationFiles, diagnostics) {
176074
+ function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
175995
176075
  const target = `include.${field}`;
175996
176076
  if (!Array.isArray(value)) {
175997
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
176077
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
175998
176078
  return;
175999
176079
  }
176000
176080
  if (value.length === 0) {
176001
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
176081
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
176002
176082
  return;
176003
176083
  }
176004
176084
  for (const pattern of value) {
176005
176085
  if (typeof pattern !== "string") {
176006
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
176086
+ diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
176007
176087
  continue;
176008
176088
  }
176009
176089
  const globResult = validateGlobPattern(pattern);
176010
176090
  if (!globResult.ok) {
176011
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
176091
+ diagnostics.push(configDiagnostic2("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
176012
176092
  continue;
176013
176093
  }
176014
- const suffixError = checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles);
176094
+ const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
176015
176095
  if (suffixError !== undefined) {
176016
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, suffixError));
176096
+ diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
176017
176097
  }
176018
176098
  }
176019
176099
  }
176020
- function checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles) {
176021
- if (!pattern.endsWith(requiredSuffix)) {
176022
- return `Pattern must end with \`${requiredSuffix}\`.`;
176100
+ function checkPatternSuffix(pattern, suffixes, excludedSuffixes) {
176101
+ if (!suffixes.some((suffix) => pattern.endsWith(suffix))) {
176102
+ const listed = suffixes.map((suffix) => `\`${suffix}\``).join(", ");
176103
+ return suffixes.length === 1 ? `Pattern must end with ${listed}.` : `Pattern must end with one of ${listed}.`;
176023
176104
  }
176024
- if (excludeDeclarationFiles && pattern.endsWith(".d.ts")) {
176025
- return "Pattern must not target `.d.ts` declaration files.";
176105
+ const excluded = excludedSuffixes.find((suffix) => pattern.endsWith(suffix));
176106
+ if (excluded !== undefined) {
176107
+ return `Pattern must not target \`${excluded}\` declaration files.`;
176026
176108
  }
176027
176109
  return;
176028
176110
  }
@@ -176030,15 +176112,15 @@ function detectLanguageOverlap(projectRoot, code) {
176030
176112
  const diagnostics = [];
176031
176113
  for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
176032
176114
  if (owners.length > 1) {
176033
- 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.`));
176115
+ 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.`));
176034
176116
  }
176035
176117
  }
176036
176118
  return diagnostics;
176037
176119
  }
176038
- function configDiagnostic(code, target, message) {
176120
+ function configDiagnostic2(code, target, message) {
176039
176121
  return { severity: "error", code, target, message };
176040
176122
  }
176041
- function isPlainObject(value) {
176123
+ function isPlainObject2(value) {
176042
176124
  return typeof value === "object" && value !== null && !Array.isArray(value);
176043
176125
  }
176044
176126
  function isStringArray(value) {
@@ -176565,6 +176647,11 @@ function positionOf(range) {
176565
176647
  // src/lsp/server.ts
176566
176648
  import { fileURLToPath as fileURLToPath3 } from "node:url";
176567
176649
 
176650
+ // src/shared/error.ts
176651
+ function reasonOf(error) {
176652
+ return error instanceof Error ? error.message : String(error);
176653
+ }
176654
+
176568
176655
  // src/lsp/position.ts
176569
176656
  function toLspPosition(position) {
176570
176657
  return { line: position.line - 1, character: position.column - 1 };
@@ -176948,31 +177035,27 @@ function renderCodeSignatures(state, counterparts) {
176948
177035
  const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
176949
177036
  const fenced = signature.length > 0 ? `
176950
177037
 
176951
- \`\`\`${FENCE_LANGUAGE[symbol.language]}
177038
+ \`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
176952
177039
  ${signature}
176953
177040
  \`\`\`` : "";
176954
177041
  blocks.push(`**${symbol.endpoint}**${fenced}`);
176955
177042
  }
176956
177043
  return blocks.length > 0 ? blocks.join(DIVIDER) : null;
176957
177044
  }
176958
- var FENCE_LANGUAGE = {
176959
- typescript: "ts",
176960
- swift: "swift",
176961
- dart: "dart",
176962
- rust: "rust"
176963
- };
176964
177045
  var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
177046
+ var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
176965
177047
  function withoutLeadingComments(text, language) {
177048
+ const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
176966
177049
  let index = 0;
176967
177050
  for (;; ) {
176968
177051
  while (index < text.length && /\s/.test(text[index] ?? "")) {
176969
177052
  index += 1;
176970
177053
  }
176971
- if (text.startsWith("//", index)) {
177054
+ if (text.startsWith(lineComment, index)) {
176972
177055
  const newline = text.indexOf(`
176973
177056
  `, index);
176974
177057
  index = newline === -1 ? text.length : newline + 1;
176975
- } else if (text.startsWith("/*", index)) {
177058
+ } else if (lineComment === "//" && text.startsWith("/*", index)) {
176976
177059
  index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
176977
177060
  } else {
176978
177061
  return text.slice(index);
@@ -177513,24 +177596,205 @@ function manifestLink(source, target, entry, side) {
177513
177596
  };
177514
177597
  }
177515
177598
 
177599
+ // src/shared/cancelable.ts
177600
+ function deferred() {
177601
+ let settle;
177602
+ const promise = new Promise((resolve2, reject) => {
177603
+ settle = { resolve: resolve2, reject };
177604
+ });
177605
+ return {
177606
+ promise,
177607
+ resolve: (value) => settle?.resolve(value),
177608
+ reject: (error) => settle?.reject(error)
177609
+ };
177610
+ }
177611
+ function settledCancelable(value) {
177612
+ return { promise: Promise.resolve(value), cancel: () => {
177613
+ return;
177614
+ } };
177615
+ }
177616
+ function mapCancelable(task, map) {
177617
+ const mapped = deferred();
177618
+ let cancelled = false;
177619
+ task.promise.then((value) => {
177620
+ if (cancelled) {
177621
+ throw abortError();
177622
+ }
177623
+ return map(value);
177624
+ }).then(mapped.resolve, mapped.reject);
177625
+ return {
177626
+ promise: mapped.promise,
177627
+ cancel() {
177628
+ cancelled = true;
177629
+ mapped.reject(abortError());
177630
+ task.cancel();
177631
+ }
177632
+ };
177633
+ }
177634
+ function abortError() {
177635
+ const error = new Error("The operation was cancelled");
177636
+ error.name = "AbortError";
177637
+ return error;
177638
+ }
177639
+ function isAbortError(error) {
177640
+ return error instanceof Error && error.name === "AbortError";
177641
+ }
177642
+ function cancelableSequence(body) {
177643
+ const outcome = deferred();
177644
+ let cancelled = false;
177645
+ let running;
177646
+ const step = async (next) => {
177647
+ if (cancelled) {
177648
+ if (typeof next !== "function") {
177649
+ next.promise.catch(() => {
177650
+ return;
177651
+ });
177652
+ next.cancel();
177653
+ }
177654
+ throw abortError();
177655
+ }
177656
+ const task = typeof next === "function" ? next() : next;
177657
+ running = task;
177658
+ try {
177659
+ return await task.promise;
177660
+ } finally {
177661
+ if (running === task) {
177662
+ running = undefined;
177663
+ }
177664
+ }
177665
+ };
177666
+ (async () => body(step))().then(outcome.resolve, outcome.reject);
177667
+ return {
177668
+ promise: outcome.promise,
177669
+ cancel() {
177670
+ if (cancelled) {
177671
+ return;
177672
+ }
177673
+ cancelled = true;
177674
+ outcome.reject(abortError());
177675
+ running?.cancel();
177676
+ }
177677
+ };
177678
+ }
177679
+
177680
+ // src/scan/code/scan-cache.ts
177681
+ import { createHash } from "node:crypto";
177682
+ function emptyCodeScanCache() {
177683
+ return { fingerprint: "", entries: new Map };
177684
+ }
177685
+ function codeScanCacheKey(language, filePath, content, options, argv, runtime = []) {
177686
+ const contentHash = createHash("sha256").update(content).digest("hex");
177687
+ return JSON.stringify([
177688
+ language,
177689
+ filePath,
177690
+ contentHash,
177691
+ options.visibility ?? null,
177692
+ argv,
177693
+ runtime
177694
+ ]);
177695
+ }
177696
+ function isReusableScan(scan) {
177697
+ return !scan.diagnostics.some((diagnostic) => diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed");
177698
+ }
177699
+
177516
177700
  // src/scan/code/typescript.ts
177701
+ var import_typescript2 = __toESM(require_typescript(), 1);
177702
+
177703
+ // src/scan/code/typescript-diagnostics.ts
177517
177704
  var import_typescript = __toESM(require_typescript(), 1);
177518
- var LANGUAGE = "typescript";
177705
+ function scriptLanguage(filePath) {
177706
+ return LANGUAGE_SUFFIXES.javascript.some((suffix) => filePath.endsWith(suffix)) ? "javascript" : "typescript";
177707
+ }
177708
+ var LABEL = {
177709
+ typescript: "TypeScript",
177710
+ javascript: "JavaScript"
177711
+ };
177712
+ var SUPPORTED_DECLARATIONS = {
177713
+ 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.",
177714
+ 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."
177715
+ };
177716
+ function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
177717
+ const location = { filePath, line: 1, column: 1 };
177718
+ if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
177719
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
177720
+ location.line = line + 1;
177721
+ location.column = character + 1;
177722
+ }
177723
+ const language = scriptLanguage(filePath);
177724
+ const detail = diagnostic === undefined ? `${LABEL[language]} file has a syntactic parse error.` : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
177725
+ `);
177726
+ return {
177727
+ severity: "error",
177728
+ code: "code_parse_error",
177729
+ language,
177730
+ target: filePath,
177731
+ message: `${LABEL[language]} parse error: ${detail}`,
177732
+ location
177733
+ };
177734
+ }
177735
+ function unsupportedDeclarationDiagnostic(filePath, location) {
177736
+ const language = scriptLanguage(filePath);
177737
+ return {
177738
+ severity: "warning",
177739
+ code: "unsupported_declaration",
177740
+ language,
177741
+ target: filePath,
177742
+ message: `@doc is attached to an unsupported declaration. ${SUPPORTED_DECLARATIONS[language]}`,
177743
+ location
177744
+ };
177745
+ }
177746
+ function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
177747
+ const diagnostic = {
177748
+ severity: "error",
177749
+ code: "duplicate_code_symbol",
177750
+ language: scriptLanguage(location.filePath),
177751
+ target: endpoint,
177752
+ message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
177753
+ location
177754
+ };
177755
+ if (range !== undefined) {
177756
+ diagnostic.range = range;
177757
+ }
177758
+ return diagnostic;
177759
+ }
177760
+ function duplicateLinkDiagnostic(source, target, location, range) {
177761
+ const diagnostic = {
177762
+ severity: "warning",
177763
+ code: "duplicate_link",
177764
+ target,
177765
+ source,
177766
+ message: `Duplicate @doc link from ${source} to ${target}.`,
177767
+ location
177768
+ };
177769
+ if (range !== undefined) {
177770
+ diagnostic.range = range;
177771
+ }
177772
+ return diagnostic;
177773
+ }
177774
+
177775
+ // src/scan/code/typescript.ts
177519
177776
  var typeScriptAdapter = {
177520
- language: LANGUAGE,
177777
+ language: "typescript",
177521
177778
  scanFile(filePath, content, options) {
177522
177779
  return scanTypeScript(filePath, content, options);
177780
+ },
177781
+ scanFiles(files, options) {
177782
+ return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
177523
177783
  }
177524
177784
  };
177785
+ var javaScriptAdapter = {
177786
+ ...typeScriptAdapter,
177787
+ language: "javascript"
177788
+ };
177525
177789
  var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
177526
177790
  function scanTypeScript(filePath, content, options = {}) {
177527
177791
  const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
177528
- const sourceFile = import_typescript.default.createSourceFile(filePath, content, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
177529
- const parseDiagnostics = getParseDiagnostics(sourceFile);
177530
- if (parseDiagnostics.length > 0) {
177531
- const first = parseDiagnostics[0];
177792
+ const sourceFile = import_typescript2.default.createSourceFile(filePath, content, import_typescript2.default.ScriptTarget.Latest, true, scriptKindOf(filePath));
177793
+ const syntaxErrors = syntaxDiagnostics(sourceFile);
177794
+ if (syntaxErrors.length > 0) {
177795
+ const first = syntaxErrors[0];
177532
177796
  return {
177533
- language: LANGUAGE,
177797
+ language: scriptLanguage(filePath),
177534
177798
  filePath,
177535
177799
  symbols: [],
177536
177800
  undocumentedSymbols: [],
@@ -177600,11 +177864,13 @@ function scanTypeScript(filePath, content, options = {}) {
177600
177864
  }
177601
177865
  const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
177602
177866
  if (!parsed.ok) {
177603
- diagnostics.push(parsed.diagnostic);
177867
+ diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
177604
177868
  continue;
177605
177869
  }
177606
177870
  if (linkTargetsSeen.has(docTag.rawTarget)) {
177607
- diagnostics.push(duplicateLinkDiagnostic(endpoint, docTag.rawTarget, docTag.location, docTag.targetRange));
177871
+ const { rawTarget, location, targetRange } = docTag;
177872
+ const duplicate = duplicateLinkDiagnostic(endpoint, rawTarget, location, targetRange);
177873
+ diagnostics.push(linkDiagnostic(filePath, duplicate));
177608
177874
  continue;
177609
177875
  }
177610
177876
  linkTargetsSeen.add(docTag.rawTarget);
@@ -177620,7 +177886,7 @@ function scanTypeScript(filePath, content, options = {}) {
177620
177886
  }
177621
177887
  }
177622
177888
  return {
177623
- language: LANGUAGE,
177889
+ language: scriptLanguage(filePath),
177624
177890
  filePath,
177625
177891
  symbols,
177626
177892
  undocumentedSymbols,
@@ -177628,15 +177894,60 @@ function scanTypeScript(filePath, content, options = {}) {
177628
177894
  diagnostics
177629
177895
  };
177630
177896
  }
177897
+ function linkDiagnostic(filePath, diagnostic) {
177898
+ if (scriptLanguage(filePath) !== "javascript") {
177899
+ return diagnostic;
177900
+ }
177901
+ const { severity, code, ...rest } = diagnostic;
177902
+ return { severity, code, language: "javascript", ...rest };
177903
+ }
177904
+ function scriptKindOf(filePath) {
177905
+ if (scriptLanguage(filePath) === "javascript") {
177906
+ return filePath.endsWith(".jsx") ? import_typescript2.default.ScriptKind.JSX : import_typescript2.default.ScriptKind.JS;
177907
+ }
177908
+ return filePath.endsWith(".tsx") ? import_typescript2.default.ScriptKind.TSX : import_typescript2.default.ScriptKind.TS;
177909
+ }
177910
+ function syntaxDiagnostics(sourceFile) {
177911
+ const parseDiagnostics = getParseDiagnostics(sourceFile);
177912
+ if (parseDiagnostics.length > 0 || scriptLanguage(sourceFile.fileName) !== "javascript") {
177913
+ return parseDiagnostics;
177914
+ }
177915
+ return javaScriptGrammarDiagnostics(sourceFile);
177916
+ }
177631
177917
  function getParseDiagnostics(sourceFile) {
177632
177918
  const withDiagnostics = sourceFile;
177633
177919
  return withDiagnostics.parseDiagnostics ?? [];
177634
177920
  }
177921
+ function javaScriptGrammarDiagnostics(sourceFile) {
177922
+ const { fileName } = sourceFile;
177923
+ const host = {
177924
+ getSourceFile: (name) => name === fileName ? sourceFile : undefined,
177925
+ fileExists: (name) => name === fileName,
177926
+ readFile: () => {
177927
+ return;
177928
+ },
177929
+ writeFile: () => {
177930
+ return;
177931
+ },
177932
+ getDefaultLibFileName: () => "lib.d.ts",
177933
+ getCurrentDirectory: () => "/",
177934
+ getCanonicalFileName: (name) => name,
177935
+ useCaseSensitiveFileNames: () => true,
177936
+ getNewLine: () => `
177937
+ `
177938
+ };
177939
+ const program = import_typescript2.default.createProgram({
177940
+ rootNames: [fileName],
177941
+ options: { allowJs: true, noLib: true, noResolve: true, types: [] },
177942
+ host
177943
+ });
177944
+ return program.getSyntacticDiagnostics(sourceFile);
177945
+ }
177635
177946
  function collectDocTags(filePath, sourceFile, statement) {
177636
177947
  const node = jsDocCarrier(statement);
177637
177948
  const location = locationOf(filePath, sourceFile, statement);
177638
177949
  const tags = [];
177639
- for (const tag of import_typescript.default.getJSDocTags(node)) {
177950
+ for (const tag of import_typescript2.default.getJSDocTags(node)) {
177640
177951
  if (tag.tagName.escapedText !== "doc") {
177641
177952
  continue;
177642
177953
  }
@@ -177655,7 +177966,7 @@ function collectDocTags(filePath, sourceFile, statement) {
177655
177966
  return tags;
177656
177967
  }
177657
177968
  function jsDocCarrier(statement) {
177658
- if (import_typescript.default.isVariableStatement(statement)) {
177969
+ if (import_typescript2.default.isVariableStatement(statement)) {
177659
177970
  const declaration = statement.declarationList.declarations[0];
177660
177971
  if (declaration !== undefined) {
177661
177972
  return declaration;
@@ -177701,11 +178012,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
177701
178012
  }
177702
178013
  }
177703
178014
  function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
177704
- if (!import_typescript.default.isConstructorDeclaration(member)) {
178015
+ if (!import_typescript2.default.isConstructorDeclaration(member)) {
177705
178016
  return;
177706
178017
  }
177707
178018
  for (const parameter of member.parameters) {
177708
- if (!import_typescript.default.isParameterPropertyDeclaration(parameter, member)) {
178019
+ if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
177709
178020
  continue;
177710
178021
  }
177711
178022
  if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
@@ -177721,22 +178032,22 @@ function describeContainer(statement) {
177721
178032
  return { name: supportedNameNode(statement)?.text ?? null, members };
177722
178033
  }
177723
178034
  function containerMembers(statement) {
177724
- if (import_typescript.default.isClassDeclaration(statement)) {
178035
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177725
178036
  return statement.members;
177726
178037
  }
177727
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178038
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177728
178039
  return statement.members;
177729
178040
  }
177730
- if (import_typescript.default.isEnumDeclaration(statement)) {
178041
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177731
178042
  return statement.members;
177732
178043
  }
177733
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
177734
- return import_typescript.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
178044
+ if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
178045
+ return import_typescript2.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
177735
178046
  }
177736
- if (import_typescript.default.isVariableStatement(statement)) {
178047
+ if (import_typescript2.default.isVariableStatement(statement)) {
177737
178048
  const declarations = statement.declarationList.declarations;
177738
178049
  const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
177739
- return initializer !== undefined && import_typescript.default.isClassExpression(initializer) ? initializer.members : null;
178050
+ return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
177740
178051
  }
177741
178052
  return null;
177742
178053
  }
@@ -177758,24 +178069,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
177758
178069
  };
177759
178070
  }
177760
178071
  function isVisibleMember(member, visibility) {
177761
- if (hasModifier(member, import_typescript.default.SyntaxKind.PrivateKeyword)) {
178072
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
177762
178073
  return visibility.has("private");
177763
178074
  }
177764
- if (hasModifier(member, import_typescript.default.SyntaxKind.ProtectedKeyword)) {
178075
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
177765
178076
  return visibility.has("protected");
177766
178077
  }
177767
178078
  return visibility.has("public");
177768
178079
  }
177769
178080
  function memberIdentity(sourceFile, member) {
177770
- if (import_typescript.default.isConstructorDeclaration(member)) {
177771
- const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript.default.SyntaxKind.ConstructorKeyword);
178081
+ if (import_typescript2.default.isConstructorDeclaration(member)) {
178082
+ const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript2.default.SyntaxKind.ConstructorKeyword);
177772
178083
  return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
177773
178084
  }
177774
- 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)) {
178085
+ 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)) {
177775
178086
  return null;
177776
178087
  }
177777
178088
  const name = member.name;
177778
- return import_typescript.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
178089
+ return import_typescript2.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
177779
178090
  }
177780
178091
  function memberSignatureEndOffset(sourceFile, member) {
177781
178092
  const body = member.body;
@@ -177784,28 +178095,28 @@ function memberSignatureEndOffset(sourceFile, member) {
177784
178095
  function supportedNameNode(statement) {
177785
178096
  const isExported = hasExportModifier(statement);
177786
178097
  const isDefault = hasDefaultModifier(statement);
177787
- if (import_typescript.default.isFunctionDeclaration(statement)) {
178098
+ if (import_typescript2.default.isFunctionDeclaration(statement)) {
177788
178099
  if (!isExported || statement.name === undefined) {
177789
178100
  return null;
177790
178101
  }
177791
178102
  return statement.name;
177792
178103
  }
177793
- if (import_typescript.default.isClassDeclaration(statement)) {
178104
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177794
178105
  if (!isExported || statement.name === undefined) {
177795
178106
  return null;
177796
178107
  }
177797
178108
  return statement.name;
177798
178109
  }
177799
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178110
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177800
178111
  return isExported && !isDefault ? statement.name : null;
177801
178112
  }
177802
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
178113
+ if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
177803
178114
  return isExported && !isDefault ? statement.name : null;
177804
178115
  }
177805
- if (import_typescript.default.isEnumDeclaration(statement)) {
178116
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177806
178117
  return isExported && !isDefault ? statement.name : null;
177807
178118
  }
177808
- if (import_typescript.default.isVariableStatement(statement)) {
178119
+ if (import_typescript2.default.isVariableStatement(statement)) {
177809
178120
  if (!isExported) {
177810
178121
  return null;
177811
178122
  }
@@ -177815,23 +178126,23 @@ function supportedNameNode(statement) {
177815
178126
  return null;
177816
178127
  }
177817
178128
  const name = declaration.name;
177818
- return import_typescript.default.isIdentifier(name) ? name : null;
178129
+ return import_typescript2.default.isIdentifier(name) ? name : null;
177819
178130
  }
177820
178131
  return null;
177821
178132
  }
177822
178133
  function hasExportModifier(statement) {
177823
- return hasModifier(statement, import_typescript.default.SyntaxKind.ExportKeyword);
178134
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
177824
178135
  }
177825
178136
  function hasDefaultModifier(statement) {
177826
- return hasModifier(statement, import_typescript.default.SyntaxKind.DefaultKeyword);
178137
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
177827
178138
  }
177828
178139
  function hasModifier(node, kind) {
177829
- const modifiers = import_typescript.default.canHaveModifiers(node) ? import_typescript.default.getModifiers(node) : undefined;
178140
+ const modifiers = import_typescript2.default.canHaveModifiers(node) ? import_typescript2.default.getModifiers(node) : undefined;
177830
178141
  return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
177831
178142
  }
177832
178143
  function declarationLocation(filePath, sourceFile, node) {
177833
- const carrier = import_typescript.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
177834
- const name = carrier === undefined ? undefined : import_typescript.default.getNameOfDeclaration(carrier);
178144
+ const carrier = import_typescript2.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
178145
+ const name = carrier === undefined ? undefined : import_typescript2.default.getNameOfDeclaration(carrier);
177835
178146
  return locationOf(filePath, sourceFile, name ?? node);
177836
178147
  }
177837
178148
  function locationOf(filePath, sourceFile, node) {
@@ -177850,13 +178161,13 @@ function rangeOfNode(sourceFile, node) {
177850
178161
  return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
177851
178162
  }
177852
178163
  function signatureEndOffset(sourceFile, statement) {
177853
- if (import_typescript.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
178164
+ if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
177854
178165
  return statement.body.getStart(sourceFile);
177855
178166
  }
177856
- if (import_typescript.default.isClassDeclaration(statement)) {
178167
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177857
178168
  return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
177858
178169
  }
177859
- if (import_typescript.default.isVariableStatement(statement)) {
178170
+ if (import_typescript2.default.isVariableStatement(statement)) {
177860
178171
  return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
177861
178172
  }
177862
178173
  return statement.getEnd();
@@ -177872,13 +178183,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
177872
178183
  if (initializer === undefined) {
177873
178184
  return;
177874
178185
  }
177875
- if ((import_typescript.default.isArrowFunction(initializer) || import_typescript.default.isFunctionExpression(initializer)) && import_typescript.default.isBlock(initializer.body)) {
178186
+ if ((import_typescript2.default.isArrowFunction(initializer) || import_typescript2.default.isFunctionExpression(initializer)) && import_typescript2.default.isBlock(initializer.body)) {
177876
178187
  return initializer.body.getStart(sourceFile);
177877
178188
  }
177878
- if (import_typescript.default.isObjectLiteralExpression(initializer)) {
178189
+ if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
177879
178190
  return initializer.getStart(sourceFile);
177880
178191
  }
177881
- if (import_typescript.default.isClassExpression(initializer)) {
178192
+ if (import_typescript2.default.isClassExpression(initializer)) {
177882
178193
  return classBodyStartOffset(sourceFile, initializer);
177883
178194
  }
177884
178195
  return;
@@ -177893,7 +178204,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
177893
178204
  function makeCodeSymbol(filePath, endpoint, declaration) {
177894
178205
  const symbol = {
177895
178206
  kind: "code",
177896
- language: LANGUAGE,
178207
+ language: scriptLanguage(filePath),
177897
178208
  filePath,
177898
178209
  symbolName: declaration.symbolName,
177899
178210
  canonicalId: declaration.canonicalId,
@@ -177930,217 +178241,20 @@ function firstToken(text) {
177930
178241
  }
177931
178242
  return trimmed.split(/\s+/)[0];
177932
178243
  }
177933
- function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
177934
- const location = { filePath, line: 1, column: 1 };
177935
- if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
177936
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
177937
- location.line = line + 1;
177938
- location.column = character + 1;
177939
- }
177940
- const detail = diagnostic === undefined ? "TypeScript file has a syntactic parse error." : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
177941
- `);
177942
- return {
177943
- severity: "error",
177944
- code: "code_parse_error",
177945
- language: LANGUAGE,
177946
- target: filePath,
177947
- message: `TypeScript parse error: ${detail}`,
177948
- location
177949
- };
177950
- }
177951
- function unsupportedDeclarationDiagnostic(filePath, location) {
177952
- return {
177953
- severity: "warning",
177954
- code: "unsupported_declaration",
177955
- language: LANGUAGE,
177956
- target: filePath,
177957
- 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.",
177958
- location
177959
- };
177960
- }
177961
- function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
177962
- const diagnostic = {
177963
- severity: "error",
177964
- code: "duplicate_code_symbol",
177965
- language: LANGUAGE,
177966
- target: endpoint,
177967
- message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
177968
- location
177969
- };
177970
- if (range !== undefined) {
177971
- diagnostic.range = range;
177972
- }
177973
- return diagnostic;
177974
- }
177975
- function duplicateLinkDiagnostic(source, target, location, range) {
177976
- const diagnostic = {
177977
- severity: "warning",
177978
- code: "duplicate_link",
177979
- target,
177980
- source,
177981
- message: `Duplicate @doc link from ${source} to ${target}.`,
177982
- location
177983
- };
177984
- if (range !== undefined) {
177985
- diagnostic.range = range;
177986
- }
177987
- return diagnostic;
177988
- }
177989
178244
 
177990
- // src/scan/code/worker/scanner-executable.ts
177991
- import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
177992
- import { join as join5 } from "node:path";
178245
+ // src/scan/code/worker/runtime-worker.ts
178246
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
178247
+ import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
177993
178248
 
177994
- // src/shared/error.ts
177995
- function reasonOf(error) {
177996
- return error instanceof Error ? error.message : String(error);
177997
- }
178249
+ // src/scan/code/worker/runtime-probe.ts
178250
+ import { spawnSync as spawnSync2 } from "node:child_process";
177998
178251
 
177999
- // src/shared/package-root.ts
178000
- import { existsSync, realpathSync } from "node:fs";
178252
+ // src/scan/code/worker/scanner-worker.ts
178253
+ var import__2020 = __toESM(require_2020(), 1);
178254
+ import { spawn, spawnSync } from "node:child_process";
178255
+ import { mkdirSync } from "node:fs";
178256
+ import { tmpdir } from "node:os";
178001
178257
  import { dirname, join as join4 } from "node:path";
178002
- import { fileURLToPath as fileURLToPath2 } from "node:url";
178003
- function resolvePackageRoot(moduleUrl = import.meta.url) {
178004
- const modulePath = fileURLToPath2(moduleUrl);
178005
- let resolved;
178006
- try {
178007
- resolved = realpathSync(modulePath);
178008
- } catch {
178009
- resolved = modulePath;
178010
- }
178011
- let dir = dirname(resolved);
178012
- for (;; ) {
178013
- if (existsSync(join4(dir, "templates", "skills"))) {
178014
- return dir;
178015
- }
178016
- const parent = dirname(dir);
178017
- if (parent === dir) {
178018
- return dir;
178019
- }
178020
- dir = parent;
178021
- }
178022
- }
178023
-
178024
- // src/scan/code/worker/scanner-executable.ts
178025
- var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
178026
- var SCANNER_EXECUTABLE_NAMES = {
178027
- swift: "docbridge-swift-scanner",
178028
- dart: "docbridge_dart_scanner",
178029
- rust: "docbridge-rust-scanner"
178030
- };
178031
- function scannerPlatformKey() {
178032
- return `${process.platform}-${process.arch}`;
178033
- }
178034
- function scannerExecutableName(language) {
178035
- return SCANNER_EXECUTABLE_NAMES[language];
178036
- }
178037
- function resolveScannerWorkerCommand(language, options = {}) {
178038
- const platformKey = options.platformKey ?? scannerPlatformKey();
178039
- const platformSupported = isSupportedScannerPlatformKey(platformKey);
178040
- const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
178041
- const found = candidates.find((candidate) => existsSync2(candidate));
178042
- if (found !== undefined) {
178043
- const repair = ensureExecutable(found, options.chmod ?? chmodSync);
178044
- if (!repair.ok) {
178045
- return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
178046
- }
178047
- return { ok: true, command: [found] };
178048
- }
178049
- if (!platformSupported) {
178050
- return {
178051
- ok: false,
178052
- diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178053
- };
178054
- }
178055
- return {
178056
- ok: false,
178057
- diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178058
- };
178059
- }
178060
- function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
178061
- const sourceRoot = options.sourceRoot ?? sourceRootPath();
178062
- const distRoot = options.distRoot ?? distRootPath();
178063
- const executable = scannerExecutableName(language);
178064
- if (language === "swift") {
178065
- return [
178066
- join5(sourceRoot, "packages/swift-scanner/.build/release", executable),
178067
- join5(sourceRoot, "packages/swift-scanner/.build/debug", executable),
178068
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178069
- ];
178070
- }
178071
- if (language === "rust") {
178072
- return [
178073
- join5(sourceRoot, "packages/rust-scanner/target/release", executable),
178074
- join5(sourceRoot, "packages/rust-scanner/target/debug", executable),
178075
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178076
- ];
178077
- }
178078
- return [
178079
- join5(sourceRoot, "packages/dart-scanner/bin", executable),
178080
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178081
- ];
178082
- }
178083
- function ensureExecutable(path, chmod) {
178084
- let mode;
178085
- try {
178086
- mode = statSync(path).mode;
178087
- } catch (error) {
178088
- return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
178089
- }
178090
- if (isExecutableByThisProcess(path)) {
178091
- return { ok: true };
178092
- }
178093
- try {
178094
- chmod(path, mode | 73);
178095
- } catch (error) {
178096
- return {
178097
- ok: false,
178098
- 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`
178099
- };
178100
- }
178101
- return { ok: true };
178102
- }
178103
- function isExecutableByThisProcess(path) {
178104
- try {
178105
- accessSync(path, constants.X_OK);
178106
- return true;
178107
- } catch {
178108
- return false;
178109
- }
178110
- }
178111
- function formatMode(mode) {
178112
- return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
178113
- }
178114
- function isSupportedScannerPlatformKey(platformKey) {
178115
- return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
178116
- }
178117
- function scannerRootsFromModuleUrl(moduleUrl) {
178118
- const packageRoot = resolvePackageRoot(moduleUrl);
178119
- return { distRoot: join5(packageRoot, "dist"), sourceRoot: packageRoot };
178120
- }
178121
- function sourceRootPath() {
178122
- return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
178123
- }
178124
- function distRootPath() {
178125
- return scannerRootsFromModuleUrl(import.meta.url).distRoot;
178126
- }
178127
- function scannerUnavailableDiagnostic(language, reason) {
178128
- const label = language.charAt(0).toUpperCase() + language.slice(1);
178129
- return {
178130
- severity: "error",
178131
- code: "code_scanner_unavailable",
178132
- language,
178133
- target: language,
178134
- message: `${label} scanner worker is unavailable: ${reason}`
178135
- };
178136
- }
178137
-
178138
- // src/scan/code/worker/scanner-worker.ts
178139
- var import__2020 = __toESM(require_2020(), 1);
178140
- import { spawnSync } from "node:child_process";
178141
- import { mkdirSync } from "node:fs";
178142
- import { tmpdir } from "node:os";
178143
- import { dirname as dirname2, join as join6 } from "node:path";
178144
178258
  // schemas/common-output.schema.json
178145
178259
  var common_output_schema_default = {
178146
178260
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -178188,7 +178302,17 @@ var common_output_schema_default = {
178188
178302
  target: { type: "string" },
178189
178303
  language: {
178190
178304
  type: "string",
178191
- enum: ["typescript", "swift", "dart", "rust"]
178305
+ enum: [
178306
+ "typescript",
178307
+ "swift",
178308
+ "dart",
178309
+ "rust",
178310
+ "go",
178311
+ "javascript",
178312
+ "python",
178313
+ "ruby",
178314
+ "java"
178315
+ ]
178192
178316
  },
178193
178317
  source: { type: "string" },
178194
178318
  message: { type: "string" },
@@ -178208,7 +178332,7 @@ var scanner_worker_schema_default = {
178208
178332
  $defs: {
178209
178333
  language: {
178210
178334
  type: "string",
178211
- enum: ["typescript", "swift", "dart", "rust"]
178335
+ enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
178212
178336
  },
178213
178337
  codeSymbol: {
178214
178338
  type: "object",
@@ -178324,6 +178448,8 @@ var scanner_worker_schema_default = {
178324
178448
  };
178325
178449
 
178326
178450
  // src/scan/code/worker/scanner-worker.ts
178451
+ var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
178452
+ var KILLED_WORKER_GRACE_MS = 500;
178327
178453
  function createLazyWorkerResponseValidator(compile) {
178328
178454
  let validator;
178329
178455
  return () => {
@@ -178341,15 +178467,29 @@ function compileWorkerResponseValidator() {
178341
178467
  $ref: "#/$defs/response"
178342
178468
  });
178343
178469
  }
178344
- function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178345
- const processResult = run({
178470
+ function invokeScannerWorker(request, command, run = runScannerWorkerProcess, stripEnv = []) {
178471
+ return interpretWorkerProcess(request, command, run(processInput(request, command, stripEnv)));
178472
+ }
178473
+ function invokeScannerWorkerAsync(request, command, runAsync = runScannerWorkerProcessAsync, stripEnv = []) {
178474
+ const run = runAsync(processInput(request, command, stripEnv));
178475
+ return {
178476
+ promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178477
+ cancel: () => run.cancel()
178478
+ };
178479
+ }
178480
+ function processInput(request, command, stripEnv) {
178481
+ return {
178346
178482
  command,
178347
- stdin: JSON.stringify(request)
178348
- });
178483
+ stdin: JSON.stringify(request),
178484
+ stripEnv,
178485
+ timeoutMs: workerTimeoutMs(request.files.length)
178486
+ };
178487
+ }
178488
+ function interpretWorkerProcess(request, command, processResult) {
178349
178489
  if (!processResult.ok) {
178350
178490
  return {
178351
178491
  ok: false,
178352
- diagnostic: scannerUnavailableDiagnostic2(request.language, processResult.error, command[0]),
178492
+ diagnostic: processResult.kind === "execution" ? scannerFailedDiagnostic(request.language, reasonOf(processResult.error)) : scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
178353
178493
  stderr: processResult.stderr
178354
178494
  };
178355
178495
  }
@@ -178390,42 +178530,202 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178390
178530
  }
178391
178531
  function clangModuleCachePath() {
178392
178532
  const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
178393
- return join6(tmpdir(), `docbridge-clang-module-cache-${owner}`);
178533
+ return join4(tmpdir(), `docbridge-clang-module-cache-${owner}`);
178534
+ }
178535
+ function workerProcessEnv(stripEnv = [], platform = process.platform) {
178536
+ const moduleCachePath = clangModuleCachePath();
178537
+ mkdirSync(moduleCachePath, { recursive: true });
178538
+ const fold = platform === "win32" ? (name) => name.toUpperCase() : (name) => name;
178539
+ const stripped = new Set(stripEnv.map(fold));
178540
+ const env = {};
178541
+ for (const [name, value] of Object.entries(process.env)) {
178542
+ if (value !== undefined && !stripped.has(fold(name))) {
178543
+ env[name] = value;
178544
+ }
178545
+ }
178546
+ env.CLANG_MODULE_CACHE_PATH = moduleCachePath;
178547
+ return env;
178548
+ }
178549
+ function workerTimeoutMs(fileCount) {
178550
+ return 30000 + 1000 * fileCount;
178394
178551
  }
178395
178552
  function runScannerWorkerProcess(input) {
178553
+ const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178396
178554
  try {
178397
- const moduleCachePath = clangModuleCachePath();
178398
- mkdirSync(moduleCachePath, { recursive: true });
178399
178555
  const [executable = "", ...args] = input.command;
178400
178556
  const result = spawnSync(executable, args, {
178401
- env: {
178402
- ...process.env,
178403
- CLANG_MODULE_CACHE_PATH: moduleCachePath
178404
- },
178557
+ env: workerProcessEnv(input.stripEnv),
178405
178558
  input: input.stdin,
178406
178559
  encoding: "utf8",
178407
- maxBuffer: 1024 * 1024 * 1024
178560
+ maxBuffer: maxOutputBytes,
178561
+ killSignal: "SIGKILL",
178562
+ ...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
178408
178563
  });
178409
- const stderr = result.stderr ?? "";
178410
- if (result.error !== undefined) {
178411
- return { ok: false, error: result.error, stderr };
178412
- }
178413
- if (result.status === null) {
178414
- return {
178415
- ok: false,
178416
- error: new Error(`worker terminated by signal ${result.signal ?? "unknown"}`),
178417
- stderr
178418
- };
178564
+ return syncWorkerProcessResult(result, input.timeoutMs, maxOutputBytes);
178565
+ } catch (error) {
178566
+ return { ok: false, kind: "start", error, stderr: "" };
178567
+ }
178568
+ }
178569
+ function syncWorkerProcessResult(result, timeoutMs, maxOutputBytes) {
178570
+ const stderr = result.stderr ?? "";
178571
+ if (result.error !== undefined) {
178572
+ return syncSpawnFailure(result.error, workerStarted(result), timeoutMs, maxOutputBytes, stderr);
178573
+ }
178574
+ if (Buffer.byteLength(result.stdout ?? "") + Buffer.byteLength(stderr) > maxOutputBytes) {
178575
+ return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178576
+ }
178577
+ if (typeof result.status !== "number") {
178578
+ return { ok: false, kind: "execution", error: signalError(result.signal), stderr };
178579
+ }
178580
+ return { ok: true, exitCode: result.status, stdout: result.stdout ?? "", stderr };
178581
+ }
178582
+ function runScannerWorkerProcessAsync(input) {
178583
+ const run = deferred();
178584
+ let settled = false;
178585
+ let timer;
178586
+ let grace;
178587
+ const settle = (complete) => {
178588
+ if (settled) {
178589
+ return;
178419
178590
  }
178420
- return {
178421
- ok: true,
178422
- exitCode: result.status,
178423
- stdout: result.stdout ?? "",
178424
- stderr
178425
- };
178591
+ settled = true;
178592
+ clearTimeout(timer);
178593
+ clearTimeout(grace);
178594
+ complete();
178595
+ };
178596
+ const ownGroup = process.platform !== "win32";
178597
+ let child;
178598
+ try {
178599
+ const [executable = "", ...args] = input.command;
178600
+ child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
178426
178601
  } catch (error) {
178427
- return { ok: false, error, stderr: "" };
178602
+ settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
178603
+ return { promise: run.promise, cancel: () => {
178604
+ return;
178605
+ } };
178606
+ }
178607
+ const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178608
+ const budget = { bytes: 0, limit: maxOutputBytes };
178609
+ const stdout = new OutputCollector(budget);
178610
+ const stderr = new OutputCollector(budget);
178611
+ let failure;
178612
+ const kill = (reason) => {
178613
+ failure ??= reason;
178614
+ killWorker(child, ownGroup);
178615
+ grace ??= setTimeout(() => {
178616
+ closeStreams(child);
178617
+ settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
178618
+ }, KILLED_WORKER_GRACE_MS);
178619
+ };
178620
+ let spawned = false;
178621
+ child.on("spawn", () => {
178622
+ spawned = true;
178623
+ });
178624
+ child.on("error", (error) => {
178625
+ if (!spawned) {
178626
+ settle(() => run.resolve({ ok: false, kind: "start", error, stderr: stderr.text() }));
178627
+ return;
178628
+ }
178629
+ failure ??= error;
178630
+ });
178631
+ child.stdout.on("data", (chunk) => {
178632
+ if (!stdout.add(chunk)) {
178633
+ kill(outputLimitError(maxOutputBytes));
178634
+ }
178635
+ });
178636
+ child.stderr.on("data", (chunk) => {
178637
+ if (!stderr.add(chunk)) {
178638
+ kill(outputLimitError(maxOutputBytes));
178639
+ }
178640
+ });
178641
+ child.stdin.on("error", () => {
178642
+ return;
178643
+ });
178644
+ child.on("close", (code, signal) => {
178645
+ settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
178646
+ });
178647
+ child.stdin.end(input.stdin);
178648
+ const { timeoutMs } = input;
178649
+ if (timeoutMs !== undefined) {
178650
+ timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
178651
+ }
178652
+ return {
178653
+ promise: run.promise,
178654
+ cancel() {
178655
+ settle(() => {
178656
+ killWorker(child, ownGroup);
178657
+ closeStreams(child);
178658
+ run.reject(abortError());
178659
+ });
178660
+ }
178661
+ };
178662
+ }
178663
+ function killWorker(child, ownGroup) {
178664
+ if (ownGroup && child.pid !== undefined) {
178665
+ try {
178666
+ process.kill(-child.pid, "SIGKILL");
178667
+ return;
178668
+ } catch {}
178669
+ }
178670
+ child.kill("SIGKILL");
178671
+ }
178672
+ function closeStreams(child) {
178673
+ child.stdin.destroy();
178674
+ child.stdout.destroy();
178675
+ child.stderr.destroy();
178676
+ }
178677
+
178678
+ class OutputCollector {
178679
+ budget;
178680
+ chunks = [];
178681
+ constructor(budget) {
178682
+ this.budget = budget;
178683
+ }
178684
+ add(chunk) {
178685
+ this.budget.bytes += chunk.byteLength;
178686
+ if (this.budget.bytes > this.budget.limit) {
178687
+ return false;
178688
+ }
178689
+ this.chunks.push(chunk);
178690
+ return true;
178691
+ }
178692
+ text() {
178693
+ return Buffer.concat(this.chunks).toString("utf8");
178694
+ }
178695
+ }
178696
+ function closedProcessResult(code, signal, failure, stdout, stderr) {
178697
+ if (failure !== undefined) {
178698
+ return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
178699
+ }
178700
+ if (code === null) {
178701
+ return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
178702
+ }
178703
+ return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
178704
+ }
178705
+ function workerStarted(result) {
178706
+ return typeof result.status === "number" || typeof result.signal === "string" || typeof result.pid === "number" && result.pid > 0;
178707
+ }
178708
+ function syncSpawnFailure(error, started, timeoutMs, maxOutputBytes, stderr) {
178709
+ const code = error.code;
178710
+ if (code === "ETIMEDOUT") {
178711
+ return { ok: false, kind: "execution", error: timeoutError(timeoutMs ?? 0), stderr };
178428
178712
  }
178713
+ if (code === "ENOBUFS") {
178714
+ return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178715
+ }
178716
+ return { ok: false, kind: started ? "execution" : "start", error, stderr };
178717
+ }
178718
+ function signalError(signal) {
178719
+ const name = signal ?? "unknown";
178720
+ return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
178721
+ }
178722
+ function timeoutError(timeoutMs) {
178723
+ return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
178724
+ code: "ETIMEDOUT"
178725
+ });
178726
+ }
178727
+ function outputLimitError(maxOutputBytes) {
178728
+ return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
178429
178729
  }
178430
178730
  function validateWorkerResponse(value, request) {
178431
178731
  const validator = responseValidator();
@@ -178439,129 +178739,780 @@ function validateWorkerResponse(value, request) {
178439
178739
  if (response.language !== request.language) {
178440
178740
  return "worker response language does not match the request";
178441
178741
  }
178442
- if (!responseFilesMatchRequest(response.files, request.files)) {
178443
- return "worker response files must match requested files";
178742
+ if (!responseFilesMatchRequest(response.files, request.files)) {
178743
+ return "worker response files must match requested files";
178744
+ }
178745
+ return;
178746
+ }
178747
+ function formatSchemaError(errors) {
178748
+ const error = errors?.[0];
178749
+ if (error === undefined) {
178750
+ return "worker response does not match scanner-worker.schema.json";
178751
+ }
178752
+ const path = error.instancePath || "/";
178753
+ return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
178754
+ }
178755
+ function responseFilesMatchRequest(responseFiles, requestFiles) {
178756
+ if (responseFiles.length !== requestFiles.length) {
178757
+ return false;
178758
+ }
178759
+ return responseFiles.every((file, index) => {
178760
+ if (!isRecord(file)) {
178761
+ return false;
178762
+ }
178763
+ return file.filePath === requestFiles[index]?.filePath;
178764
+ });
178765
+ }
178766
+ function isRecord(value) {
178767
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178768
+ }
178769
+ function scannerUnavailableDiagnostic(language, error, executable) {
178770
+ const label = languageLabel(language);
178771
+ return {
178772
+ severity: "error",
178773
+ code: "code_scanner_unavailable",
178774
+ language,
178775
+ target: language,
178776
+ message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
178777
+ };
178778
+ }
178779
+ function spawnFailureReason(error, executable) {
178780
+ const reason = reasonOf(error);
178781
+ if (!isExecDenied(error) || executable === undefined) {
178782
+ return reason;
178783
+ }
178784
+ 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\``;
178785
+ }
178786
+ function isExecDenied(error) {
178787
+ if (!(error instanceof Error)) {
178788
+ return false;
178789
+ }
178790
+ const code = error.code;
178791
+ return code === "EACCES" || code === "EPERM";
178792
+ }
178793
+ function scannerFailedDiagnostic(language, reason) {
178794
+ const label = languageLabel(language);
178795
+ return {
178796
+ severity: "error",
178797
+ code: "code_scanner_failed",
178798
+ language,
178799
+ target: language,
178800
+ message: `${label} scanner worker failed: ${reason}`
178801
+ };
178802
+ }
178803
+ function languageLabel(language) {
178804
+ return language.charAt(0).toUpperCase() + language.slice(1);
178805
+ }
178806
+
178807
+ // src/scan/code/worker/runtime-probe.ts
178808
+ var PROBE_TIMEOUT_MS = 1e4;
178809
+ var PROBE_MAX_OUTPUT_BYTES = 64 * 1024;
178810
+ function probeRuntime(command, stripEnv, spawn2 = spawnRuntimeProbe) {
178811
+ const [executable = "", ...args] = command;
178812
+ return classifyProbe(spawn2(executable, [...args, "--probe"], {
178813
+ env: workerProcessEnv(stripEnv),
178814
+ timeout: PROBE_TIMEOUT_MS,
178815
+ maxBuffer: PROBE_MAX_OUTPUT_BYTES
178816
+ }));
178817
+ }
178818
+ function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcessAsync) {
178819
+ const run = runAsync({
178820
+ command: [...command, "--probe"],
178821
+ stdin: "",
178822
+ stripEnv,
178823
+ timeoutMs: PROBE_TIMEOUT_MS,
178824
+ maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
178825
+ });
178826
+ return {
178827
+ promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
178828
+ cancel: () => run.cancel()
178829
+ };
178830
+ }
178831
+ function spawnResultOf(result) {
178832
+ if (result.ok) {
178833
+ return { status: result.exitCode, signal: null, stdout: result.stdout, stderr: result.stderr };
178834
+ }
178835
+ const error = result.error instanceof Error ? result.error : new Error(reasonOf(result.error));
178836
+ const { signal } = error;
178837
+ if (typeof signal === "string") {
178838
+ return { status: null, signal, stdout: "", stderr: result.stderr };
178839
+ }
178840
+ return { status: null, signal: null, stdout: "", stderr: result.stderr, error };
178841
+ }
178842
+ function classifyProbe(result) {
178843
+ if (result.error !== undefined) {
178844
+ return classifySpawnError(result.error);
178845
+ }
178846
+ if (result.status === null) {
178847
+ return { kind: "failed", reason: `probe terminated by signal ${result.signal ?? "unknown"}` };
178848
+ }
178849
+ if (result.status !== 0) {
178850
+ const detail = firstLine(result.stderr);
178851
+ return {
178852
+ kind: "failed",
178853
+ reason: `probe exited with status ${result.status}${detail === "" ? "" : `: ${detail}`}`
178854
+ };
178855
+ }
178856
+ return parseProbeOutput(result.stdout);
178857
+ }
178858
+ function classifySpawnError(error) {
178859
+ const code = error.code;
178860
+ if (code === "ETIMEDOUT") {
178861
+ return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
178862
+ }
178863
+ if (code === "ENOBUFS") {
178864
+ return {
178865
+ kind: "failed",
178866
+ reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178867
+ };
178868
+ }
178869
+ return { kind: "unstartable", reason: reasonOf(error) };
178870
+ }
178871
+ function parseProbeOutput(stdout) {
178872
+ const text = stdout.trim();
178873
+ let value;
178874
+ try {
178875
+ value = JSON.parse(text);
178876
+ } catch {
178877
+ return malformed(text);
178878
+ }
178879
+ if (!isRecord2(value)) {
178880
+ return malformed(text);
178881
+ }
178882
+ if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
178883
+ return { kind: "ok", runtime: value.runtime, version: value.version };
178884
+ }
178885
+ if (value.ok === false && typeof value.reason === "string") {
178886
+ return { kind: "rejected", reason: value.reason };
178887
+ }
178888
+ return malformed(text);
178889
+ }
178890
+ function malformed(text) {
178891
+ const shown = text.length > 200 ? `${text.slice(0, 200)}...` : text;
178892
+ return { kind: "failed", reason: `malformed probe output: ${JSON.stringify(shown)}` };
178893
+ }
178894
+ function firstLine(text) {
178895
+ return text.trim().split(/\r?\n/u)[0] ?? "";
178896
+ }
178897
+ function isRecord2(value) {
178898
+ return typeof value === "object" && value !== null && !Array.isArray(value);
178899
+ }
178900
+ function spawnRuntimeProbe(executable, args, options) {
178901
+ try {
178902
+ const result = spawnSync2(executable, args, {
178903
+ env: options.env,
178904
+ timeout: options.timeout,
178905
+ killSignal: "SIGKILL",
178906
+ maxBuffer: options.maxBuffer,
178907
+ input: "",
178908
+ encoding: "utf8",
178909
+ windowsHide: true
178910
+ });
178911
+ const stdout = result.stdout ?? "";
178912
+ const stderr = result.stderr ?? "";
178913
+ 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;
178914
+ return {
178915
+ status: result.status,
178916
+ signal: result.signal,
178917
+ stdout,
178918
+ stderr,
178919
+ ...error === undefined ? {} : { error }
178920
+ };
178921
+ } catch (error) {
178922
+ return {
178923
+ status: null,
178924
+ signal: null,
178925
+ stdout: "",
178926
+ stderr: "",
178927
+ error: error instanceof Error ? error : new Error(String(error))
178928
+ };
178929
+ }
178930
+ }
178931
+
178932
+ // src/scan/code/worker/scanner-executable.ts
178933
+ import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
178934
+ import { join as join6 } from "node:path";
178935
+
178936
+ // src/shared/package-root.ts
178937
+ import { existsSync, realpathSync } from "node:fs";
178938
+ import { dirname as dirname2, join as join5 } from "node:path";
178939
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
178940
+ function resolvePackageRoot(moduleUrl = import.meta.url) {
178941
+ const modulePath = fileURLToPath2(moduleUrl);
178942
+ let resolved;
178943
+ try {
178944
+ resolved = realpathSync(modulePath);
178945
+ } catch {
178946
+ resolved = modulePath;
178947
+ }
178948
+ let dir = dirname2(resolved);
178949
+ for (;; ) {
178950
+ if (existsSync(join5(dir, "templates", "skills"))) {
178951
+ return dir;
178952
+ }
178953
+ const parent = dirname2(dir);
178954
+ if (parent === dir) {
178955
+ return dir;
178956
+ }
178957
+ dir = parent;
178958
+ }
178959
+ }
178960
+
178961
+ // src/scan/code/worker/scanner-executable.ts
178962
+ var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
178963
+ var SCANNER_EXECUTABLE_NAMES = {
178964
+ swift: "docbridge-swift-scanner",
178965
+ dart: "docbridge_dart_scanner",
178966
+ rust: "docbridge-rust-scanner",
178967
+ go: "docbridge-go-scanner"
178968
+ };
178969
+ function scannerPlatformKey() {
178970
+ return `${process.platform}-${process.arch}`;
178971
+ }
178972
+ function scannerExecutableName(language) {
178973
+ return SCANNER_EXECUTABLE_NAMES[language];
178974
+ }
178975
+ function resolveScannerWorkerCommand(language, options = {}) {
178976
+ const platformKey = options.platformKey ?? scannerPlatformKey();
178977
+ const platformSupported = isSupportedScannerPlatformKey(platformKey);
178978
+ const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
178979
+ const found = candidates.find((candidate) => existsSync2(candidate));
178980
+ if (found !== undefined) {
178981
+ const repair = ensureExecutable(found, options.chmod ?? chmodSync);
178982
+ if (!repair.ok) {
178983
+ return { ok: false, diagnostic: scannerUnavailableDiagnostic2(language, repair.reason) };
178984
+ }
178985
+ return { ok: true, command: [found] };
178986
+ }
178987
+ if (!platformSupported) {
178988
+ return {
178989
+ ok: false,
178990
+ diagnostic: scannerUnavailableDiagnostic2(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178991
+ };
178992
+ }
178993
+ return {
178994
+ ok: false,
178995
+ diagnostic: scannerUnavailableDiagnostic2(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178996
+ };
178997
+ }
178998
+ function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
178999
+ const sourceRoot = options.sourceRoot ?? sourceRootPath();
179000
+ const distRoot = options.distRoot ?? distRootPath();
179001
+ const executable = scannerExecutableName(language);
179002
+ if (language === "swift") {
179003
+ return [
179004
+ join6(sourceRoot, "packages/swift-scanner/.build/release", executable),
179005
+ join6(sourceRoot, "packages/swift-scanner/.build/debug", executable),
179006
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179007
+ ];
179008
+ }
179009
+ if (language === "rust") {
179010
+ return [
179011
+ join6(sourceRoot, "packages/rust-scanner/target/release", executable),
179012
+ join6(sourceRoot, "packages/rust-scanner/target/debug", executable),
179013
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179014
+ ];
179015
+ }
179016
+ if (language === "go") {
179017
+ return [
179018
+ join6(sourceRoot, "packages/go-scanner/bin", executable),
179019
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179020
+ ];
179021
+ }
179022
+ return [
179023
+ join6(sourceRoot, "packages/dart-scanner/bin", executable),
179024
+ ...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
179025
+ ];
179026
+ }
179027
+ function ensureExecutable(path, chmod) {
179028
+ let mode;
179029
+ try {
179030
+ mode = statSync(path).mode;
179031
+ } catch (error) {
179032
+ return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
179033
+ }
179034
+ if (isExecutableByThisProcess(path)) {
179035
+ return { ok: true };
179036
+ }
179037
+ try {
179038
+ chmod(path, mode | 73);
179039
+ } catch (error) {
179040
+ return {
179041
+ ok: false,
179042
+ 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`
179043
+ };
179044
+ }
179045
+ return { ok: true };
179046
+ }
179047
+ function isExecutableByThisProcess(path) {
179048
+ try {
179049
+ accessSync(path, constants.X_OK);
179050
+ return true;
179051
+ } catch {
179052
+ return false;
179053
+ }
179054
+ }
179055
+ function formatMode(mode) {
179056
+ return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
179057
+ }
179058
+ function isSupportedScannerPlatformKey(platformKey) {
179059
+ return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
179060
+ }
179061
+ function scannerRootsFromModuleUrl(moduleUrl) {
179062
+ const packageRoot = resolvePackageRoot(moduleUrl);
179063
+ return { distRoot: join6(packageRoot, "dist"), sourceRoot: packageRoot };
179064
+ }
179065
+ function sourceRootPath() {
179066
+ return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
179067
+ }
179068
+ function distRootPath() {
179069
+ return scannerRootsFromModuleUrl(import.meta.url).distRoot;
179070
+ }
179071
+ function scannerUnavailableDiagnostic2(language, reason) {
179072
+ const label = language.charAt(0).toUpperCase() + language.slice(1);
179073
+ return {
179074
+ severity: "error",
179075
+ code: "code_scanner_unavailable",
179076
+ language,
179077
+ target: language,
179078
+ message: `${label} scanner worker is unavailable: ${reason}`
179079
+ };
179080
+ }
179081
+
179082
+ // src/scan/code/worker/runtime-worker.ts
179083
+ var RUNTIME_WORKERS = {
179084
+ python: {
179085
+ label: "Python",
179086
+ runtimeName: "CPython",
179087
+ runtimeId: "cpython",
179088
+ floor: [3, 10],
179089
+ floorText: "3.10",
179090
+ candidates: (platform) => platform === "win32" ? [["py", "-3"], ["python"]] : [["python3"], ["python"]],
179091
+ flags: ["-I", "-S"],
179092
+ stripEnv: ["PYTHONPATH", "PYTHONSTARTUP", "PYTHONHOME", "PYTHONSAFEPATH"],
179093
+ entrypoint: {
179094
+ source: "packages/python-scanner/docbridge_python_scanner.py",
179095
+ dist: "workers/python/docbridge_python_scanner.py"
179096
+ }
179097
+ },
179098
+ ruby: {
179099
+ label: "Ruby",
179100
+ runtimeName: "CRuby",
179101
+ runtimeId: "cruby",
179102
+ floor: [3, 3],
179103
+ floorText: "3.3",
179104
+ candidates: () => [["ruby"]],
179105
+ flags: ["--disable=gems,did_you_mean,error_highlight", "-W0"],
179106
+ stripEnv: ["RUBYOPT", "RUBYLIB", "PRISM_FFI_BACKEND"],
179107
+ entrypoint: {
179108
+ source: "packages/ruby-scanner/bin/docbridge-ruby-scanner",
179109
+ dist: "workers/ruby/bin/docbridge-ruby-scanner"
179110
+ }
179111
+ },
179112
+ java: {
179113
+ label: "Java",
179114
+ runtimeName: "JDK",
179115
+ runtimeId: "jdk",
179116
+ floor: [17, 0],
179117
+ floorText: "17",
179118
+ candidates: () => [["java"]],
179119
+ flags: ["-Xshare:auto", "-XX:TieredStopAtLevel=1", "-XX:+UseSerialGC", "-jar"],
179120
+ stripEnv: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS"],
179121
+ entrypoint: {
179122
+ source: "packages/java-scanner/build/docbridge-java-scanner.jar",
179123
+ dist: "workers/java/docbridge-java-scanner.jar"
179124
+ },
179125
+ buildHint: "run `just build-java-scanner` in a source checkout"
179126
+ }
179127
+ };
179128
+ var probeCache = new Map;
179129
+ var probeCacheGeneration = 0;
179130
+ function clearRuntimeProbeCache() {
179131
+ probeCache.clear();
179132
+ probeCacheGeneration += 1;
179133
+ }
179134
+ function rememberProbe(key, generation, outcome) {
179135
+ if (generation === probeCacheGeneration) {
179136
+ probeCache.set(key, outcome);
179137
+ }
179138
+ }
179139
+ function resolveRuntimeWorkerCommand(language, options) {
179140
+ const env = options.env ?? process.env;
179141
+ const probe = options.probe ?? probeRuntime;
179142
+ const steps = resolutionSteps(language, options, env);
179143
+ let step = steps.next();
179144
+ while (step.done !== true) {
179145
+ step = steps.next(cachedProbe(step.value, env, probe));
179146
+ }
179147
+ return step.value;
179148
+ }
179149
+ function resolveRuntimeWorkerCommandAsync(language, options) {
179150
+ const env = options.env ?? process.env;
179151
+ const probeAsync = options.probeAsync ?? probeRuntimeAsync;
179152
+ return cancelableSequence(async (run) => {
179153
+ const steps = resolutionSteps(language, options, env);
179154
+ let step = steps.next();
179155
+ while (step.done !== true) {
179156
+ const request = step.value;
179157
+ step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
179158
+ }
179159
+ return step.value;
179160
+ });
179161
+ }
179162
+ function* resolutionSteps(language, options, env) {
179163
+ const spec = RUNTIME_WORKERS[language];
179164
+ const entrypoint = findEntrypoint(spec, options);
179165
+ if (!entrypoint.ok) {
179166
+ return failure(spec, language, "code_scanner_unavailable", entrypoint.reason);
179167
+ }
179168
+ const platform = options.platform ?? process.platform;
179169
+ const commandFor = (runtime) => [...runtime, ...spec.flags, entrypoint.path];
179170
+ const usable = (command, runtime, verdict) => ({
179171
+ ok: true,
179172
+ command,
179173
+ stripEnv: spec.stripEnv,
179174
+ runtime: [verdict.runtime, verdict.version, locateExecutable(runtime[0] ?? "", env, platform)]
179175
+ });
179176
+ const override = explicitOverride(language, options, env);
179177
+ if (override !== undefined) {
179178
+ const runtime = override.argv.map((part, index) => index === 0 ? resolveExecutable(part, options.projectRoot) : part);
179179
+ const command = commandFor(runtime);
179180
+ const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
179181
+ if (verdict.ok) {
179182
+ return usable(command, runtime, verdict);
179183
+ }
179184
+ return failure(spec, language, verdict.code, `${override.source} (${runtime.join(" ")}) ${verdict.reason}; no other runtime is tried ` + `while ${override.source} is set`);
179185
+ }
179186
+ const rejections = [];
179187
+ for (const runtime of spec.candidates(platform)) {
179188
+ const command = commandFor(runtime);
179189
+ const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
179190
+ if (verdict.ok) {
179191
+ return usable(command, runtime, verdict);
179192
+ }
179193
+ rejections.push({ runtime, verdict });
179194
+ }
179195
+ const outcomes = rejections.map(({ runtime, verdict }) => `${runtime.join(" ")} ${verdict.reason}`).join("; ");
179196
+ const code = rejections.find(({ verdict }) => verdict.startable)?.verdict.code ?? "code_scanner_unavailable";
179197
+ 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)}`);
179198
+ }
179199
+ function findEntrypoint(spec, options) {
179200
+ const roots = scannerRootsFromModuleUrl(import.meta.url);
179201
+ const source = join7(options.sourceRoot ?? roots.sourceRoot, spec.entrypoint.source);
179202
+ const dist = join7(options.distRoot ?? roots.distRoot, spec.entrypoint.dist);
179203
+ const found = [source, dist].find((path) => existsSync3(path));
179204
+ if (found !== undefined) {
179205
+ return { ok: true, path: found };
179206
+ }
179207
+ const hint = spec.buildHint === undefined ? "" : `; ${spec.buildHint}`;
179208
+ return {
179209
+ ok: false,
179210
+ reason: `the bundled worker is missing; looked for ${source} and ${dist}${hint}`
179211
+ };
179212
+ }
179213
+ function explicitOverride(language, options, env) {
179214
+ if (options.command !== undefined && options.command.length > 0) {
179215
+ return { source: `scanners.${language}.command`, argv: options.command };
179216
+ }
179217
+ const variable = environmentVariable(language);
179218
+ const value = env[variable];
179219
+ if (value !== undefined && value !== "") {
179220
+ return { source: variable, argv: [value] };
178444
179221
  }
178445
179222
  return;
178446
179223
  }
178447
- function formatSchemaError(errors) {
178448
- const error = errors?.[0];
178449
- if (error === undefined) {
178450
- return "worker response does not match scanner-worker.schema.json";
179224
+ function environmentVariable(language) {
179225
+ return `DOCBRIDGE_${language.toUpperCase()}_RUNTIME`;
179226
+ }
179227
+ function resolveExecutable(executable, projectRoot) {
179228
+ if (isAbsolute2(executable) || !/[/\\]/u.test(executable)) {
179229
+ return executable;
178451
179230
  }
178452
- const path = error.instancePath || "/";
178453
- return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
179231
+ return resolve2(projectRoot, executable);
178454
179232
  }
178455
- function responseFilesMatchRequest(responseFiles, requestFiles) {
178456
- if (responseFiles.length !== requestFiles.length) {
178457
- return false;
179233
+ function locateExecutable(executable, env, platform) {
179234
+ const candidates = /[/\\]/u.test(executable) ? [executable] : pathCandidates(executable, env, platform);
179235
+ const found = candidates.find((candidate) => isExecutableFile(candidate, platform));
179236
+ if (found === undefined) {
179237
+ return executable;
178458
179238
  }
178459
- return responseFiles.every((file, index) => {
178460
- if (!isRecord(file)) {
179239
+ try {
179240
+ return realpathSync2(found);
179241
+ } catch {
179242
+ return found;
179243
+ }
179244
+ }
179245
+ function pathCandidates(name, env, platform) {
179246
+ const windows = platform === "win32";
179247
+ const directories = (env.PATH ?? "").split(windows ? ";" : ":").filter((dir) => dir !== "");
179248
+ const extensions = windows && extname(name) === "" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
179249
+ return directories.flatMap((directory) => extensions.map((extension) => join7(directory, `${name}${extension}`)));
179250
+ }
179251
+ function isExecutableFile(path, platform) {
179252
+ try {
179253
+ if (!statSync2(path).isFile()) {
178461
179254
  return false;
178462
179255
  }
178463
- return file.filePath === requestFiles[index]?.filePath;
178464
- });
179256
+ if (platform !== "win32") {
179257
+ accessSync2(path, constants2.X_OK);
179258
+ }
179259
+ return true;
179260
+ } catch {
179261
+ return false;
179262
+ }
178465
179263
  }
178466
- function isRecord(value) {
178467
- return typeof value === "object" && value !== null && !Array.isArray(value);
179264
+ function cachedProbe({ command, stripEnv }, env, probe) {
179265
+ const key = probeCacheKey(command, env);
179266
+ const cached = probeCache.get(key);
179267
+ if (cached !== undefined) {
179268
+ return cached;
179269
+ }
179270
+ const generation = probeCacheGeneration;
179271
+ const outcome = probe(command, stripEnv);
179272
+ rememberProbe(key, generation, outcome);
179273
+ return outcome;
178468
179274
  }
178469
- function scannerUnavailableDiagnostic2(language, error, executable) {
178470
- const label = languageLabel(language);
178471
- return {
178472
- severity: "error",
178473
- code: "code_scanner_unavailable",
178474
- language,
178475
- target: language,
178476
- message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
178477
- };
179275
+ function cachedProbeAsync({ command, stripEnv }, env, probeAsync) {
179276
+ const key = probeCacheKey(command, env);
179277
+ const cached = probeCache.get(key);
179278
+ if (cached !== undefined) {
179279
+ return settledCancelable(cached);
179280
+ }
179281
+ const generation = probeCacheGeneration;
179282
+ return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
179283
+ rememberProbe(key, generation, outcome);
179284
+ return outcome;
179285
+ });
178478
179286
  }
178479
- function spawnFailureReason(error, executable) {
178480
- const reason = reasonOf(error);
178481
- if (!isExecDenied(error) || executable === undefined) {
178482
- return reason;
179287
+ function probeCacheKey(command, env) {
179288
+ const docbridgeVariables = Object.entries(env).filter(([name]) => name.startsWith("DOCBRIDGE_")).toSorted(([left], [right]) => left.localeCompare(right));
179289
+ return JSON.stringify([command, env.PATH ?? "", docbridgeVariables]);
179290
+ }
179291
+ function judge(spec, outcome) {
179292
+ switch (outcome.kind) {
179293
+ case "unstartable":
179294
+ return rejected("code_scanner_unavailable", false, `could not be started: ${outcome.reason}`);
179295
+ case "failed":
179296
+ return rejected("code_scanner_failed", true, `failed its probe: ${outcome.reason}`);
179297
+ case "rejected":
179298
+ return rejected("code_scanner_unavailable", true, `is not a usable ${spec.runtimeName} ${spec.floorText} or later: ${outcome.reason}`);
179299
+ case "ok":
179300
+ return judgeVersion(spec, outcome.runtime, outcome.version);
178483
179301
  }
178484
- 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\``;
178485
179302
  }
178486
- function isExecDenied(error) {
178487
- if (!(error instanceof Error)) {
178488
- return false;
179303
+ function judgeVersion(spec, runtime, version) {
179304
+ if (runtime !== spec.runtimeId) {
179305
+ return rejected("code_scanner_unavailable", true, `reports runtime ${runtime} ${version}, not ${spec.runtimeName}`);
178489
179306
  }
178490
- const code = error.code;
178491
- return code === "EACCES" || code === "EPERM";
179307
+ const match = /^(\d+)(?:\.(\d+))?/u.exec(version);
179308
+ if (match === null) {
179309
+ return rejected("code_scanner_failed", true, `reported an unreadable version: ${version}`);
179310
+ }
179311
+ const major = Number(match[1]);
179312
+ const minor = Number(match[2] ?? "0");
179313
+ const [floorMajor, floorMinor] = spec.floor;
179314
+ if (major < floorMajor || major === floorMajor && minor < floorMinor) {
179315
+ return rejected("code_scanner_unavailable", true, `is ${spec.runtimeName} ${version}, below the ${spec.floorText} floor`);
179316
+ }
179317
+ return { ok: true, runtime, version };
178492
179318
  }
178493
- function scannerFailedDiagnostic(language, reason) {
178494
- const label = languageLabel(language);
179319
+ function rejected(code, startable, reason) {
179320
+ return { ok: false, code, startable, reason };
179321
+ }
179322
+ function failure(spec, language, code, reason) {
179323
+ const state = code === "code_scanner_unavailable" ? "is unavailable" : "failed";
178495
179324
  return {
178496
- severity: "error",
178497
- code: "code_scanner_failed",
178498
- language,
178499
- target: language,
178500
- message: `${label} scanner worker failed: ${reason}`
179325
+ ok: false,
179326
+ diagnostic: {
179327
+ severity: "error",
179328
+ code,
179329
+ ...isCodeLanguage(language) ? { language } : {},
179330
+ target: language,
179331
+ message: `${spec.label} scanner worker ${state}: ${reason}`
179332
+ }
178501
179333
  };
178502
179334
  }
178503
- function languageLabel(language) {
178504
- return language.charAt(0).toUpperCase() + language.slice(1);
178505
- }
178506
179335
 
178507
179336
  // src/scan/code/dispatch.ts
178508
179337
  function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
179338
+ const { commandAsync } = adapterOptions;
179339
+ const bound = (context) => boundWorkerAdapter(language, workerCommand(command(context)), adapterOptions).adapter;
179340
+ const prepare = (context) => mapCancelable(commandAsync?.(context) ?? settledCancelable(command(context)), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
179341
+ const scanBatchAsync = (files, options, context) => {
179342
+ if (files.length === 0) {
179343
+ return settledCancelable([]);
179344
+ }
179345
+ if (commandAsync === undefined) {
179346
+ return bound(context).scanFilesAsync(files, options, context);
179347
+ }
179348
+ return cancelableSequence(async (step) => {
179349
+ const { adapter } = await step(() => prepare(context));
179350
+ return step(() => adapter.scanFilesAsync(files, options, context));
179351
+ });
179352
+ };
178509
179353
  return {
179354
+ language,
179355
+ scanFile: (filePath, content, options, context) => bound(context).scanFile(filePath, content, options, context),
179356
+ scanFiles: (files, options, context) => files.length === 0 ? [] : bound(context).scanFiles(files, options, context),
179357
+ scanFilesAsync: scanBatchAsync,
179358
+ prepare
179359
+ };
179360
+ }
179361
+ function boundWorkerAdapter(language, resolved, adapterOptions) {
179362
+ const argv = resolved.ok ? resolved.command : [];
179363
+ const runtime = resolved.ok ? resolved.runtime : [];
179364
+ const adapter = {
178510
179365
  language,
178511
179366
  scanFile(filePath, content, options, context) {
178512
- const commandResolution = normalizeScannerCommand(command(context.projectRoot));
178513
- if (!commandResolution.ok) {
178514
- return {
178515
- ...emptyScan(language, filePath),
178516
- diagnostics: [fileScopedScannerDiagnostic(commandResolution.diagnostic, filePath)]
178517
- };
179367
+ const [scan] = adapter.scanFiles([{ filePath, content }], options, context);
179368
+ return scan ?? emptyScan(language, filePath);
179369
+ },
179370
+ scanFiles(files, options, context) {
179371
+ if (files.length === 0) {
179372
+ return [];
178518
179373
  }
178519
- const result = invokeScannerWorker({
178520
- schemaVersion: 1,
178521
- requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
178522
- language,
178523
- projectRoot: context.projectRoot,
178524
- files: [{ filePath, content }],
178525
- options
178526
- }, commandResolution.command, adapterOptions.run);
178527
- if (result.ok) {
178528
- const scan = result.codeFiles[0];
178529
- return scan ?? emptyScan(language, filePath);
179374
+ if (!resolved.ok) {
179375
+ return failedBatch(language, files, resolved.diagnostic);
178530
179376
  }
178531
- return {
178532
- ...emptyScan(language, filePath),
178533
- diagnostics: [fileScopedScannerDiagnostic(result.diagnostic, filePath)]
178534
- };
178535
- }
179377
+ const result = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
179378
+ return result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic);
179379
+ },
179380
+ scanFilesAsync(files, options, context) {
179381
+ if (files.length === 0) {
179382
+ return settledCancelable([]);
179383
+ }
179384
+ if (!resolved.ok) {
179385
+ return settledCancelable(failedBatch(language, files, resolved.diagnostic));
179386
+ }
179387
+ const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
179388
+ return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
179389
+ },
179390
+ prepare: () => settledCancelable({ argv, runtime, adapter })
178536
179391
  };
179392
+ return { argv, runtime, adapter };
178537
179393
  }
178538
179394
  function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
178539
- const codeFiles = [];
178540
- const diagnostics = [];
179395
+ const plan = planCodeScan(files, codeInclude, read, options.onContent);
179396
+ const context = scanContext(projectRoot, options);
179397
+ for (const batch of plan.batches) {
179398
+ const adapter = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179399
+ fillBatch(plan, batch, adapter.scanFiles(batch.files, batch.options, context));
179400
+ }
179401
+ return assemblePlan(plan);
179402
+ }
179403
+ function scanContext(projectRoot, options) {
179404
+ return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
179405
+ }
179406
+ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
179407
+ const plan = planCodeScan(files, codeInclude, read, options.onContent);
179408
+ const context = scanContext(projectRoot, options);
179409
+ return cancelableSequence(async (step) => {
179410
+ const cache = new Map;
179411
+ for (const batch of plan.batches) {
179412
+ const base = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179413
+ const prepare = base.prepare?.bind(base);
179414
+ const { argv, runtime, adapter } = prepare === undefined ? { argv: [], adapter: base } : await step(() => prepare(context));
179415
+ const keys = batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
179416
+ const cached = keys.map((key) => options.cache?.get(key));
179417
+ const missing = batch.files.filter((_, index) => cached[index] === undefined);
179418
+ const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
179419
+ let next = 0;
179420
+ const results = cached.map((hit) => hit ?? scanned[next++]);
179421
+ fillBatch(plan, batch, results);
179422
+ results.forEach((scan, index) => {
179423
+ const key = keys[index];
179424
+ if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
179425
+ cache.set(key, scan);
179426
+ }
179427
+ });
179428
+ }
179429
+ return { ...assemblePlan(plan), cache };
179430
+ });
179431
+ }
179432
+ function scanFilesAsync(adapter, files, options, context) {
179433
+ return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
179434
+ }
179435
+ var builtInAdapters = {
179436
+ typescript: typeScriptAdapter,
179437
+ swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
179438
+ dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
179439
+ rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
179440
+ go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go")),
179441
+ javascript: javaScriptAdapter,
179442
+ python: runtimeWorkerAdapter("python"),
179443
+ ruby: runtimeWorkerAdapter("ruby")
179444
+ };
179445
+ function runtimeWorkerAdapter(language) {
179446
+ const resolution = ({ projectRoot, scanners }) => {
179447
+ const command = scanners?.[language]?.command;
179448
+ return command === undefined ? { projectRoot } : { projectRoot, command };
179449
+ };
179450
+ return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
179451
+ }
179452
+ function planCodeScan(files, codeInclude, read, onContent) {
179453
+ const slots = [];
179454
+ const batches = new Map;
178541
179455
  for (const { language, relPath } of files) {
178542
179456
  const result = read(relPath);
178543
179457
  if (!result.ok) {
178544
- diagnostics.push(result.diagnostic);
179458
+ slots.push({ read: false, diagnostic: result.diagnostic });
178545
179459
  continue;
178546
179460
  }
178547
- options.onContent?.(relPath, result.content);
178548
- const adapter = options.adapters?.[language] ?? builtInAdapters[language];
178549
- const entry = codeInclude[language];
178550
- const scanOptions = entry?.visibility !== undefined ? { visibility: entry.visibility } : {};
178551
- const scan = adapter.scanFile(relPath, result.content, scanOptions, {
178552
- projectRoot
178553
- });
179461
+ onContent?.(relPath, result.content);
179462
+ let batch = batches.get(language);
179463
+ if (batch === undefined) {
179464
+ batch = { language, options: scanOptionsFor(codeInclude, language), files: [], slots: [] };
179465
+ batches.set(language, batch);
179466
+ }
179467
+ batch.files.push({ filePath: relPath, content: result.content });
179468
+ batch.slots.push(slots.length);
179469
+ slots.push({ read: true, language, relPath });
179470
+ }
179471
+ return { slots, batches: [...batches.values()] };
179472
+ }
179473
+ function fillBatch(plan, batch, results) {
179474
+ batch.slots.forEach((slotIndex, resultIndex) => {
179475
+ const slot = plan.slots[slotIndex];
179476
+ const scan = results[resultIndex];
179477
+ if (slot?.read === true && scan !== undefined) {
179478
+ slot.scan = scan;
179479
+ }
179480
+ });
179481
+ }
179482
+ function assemblePlan(plan) {
179483
+ const codeFiles = [];
179484
+ const diagnostics = [];
179485
+ for (const slot of plan.slots) {
179486
+ if (!slot.read) {
179487
+ diagnostics.push(slot.diagnostic);
179488
+ continue;
179489
+ }
179490
+ const scan = slot.scan ?? emptyScan(slot.language, slot.relPath);
178554
179491
  diagnostics.push(...scan.diagnostics);
178555
179492
  codeFiles.push(scan);
178556
179493
  }
178557
179494
  return { codeFiles, diagnostics };
178558
179495
  }
178559
- var builtInAdapters = {
178560
- typescript: typeScriptAdapter,
178561
- swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
178562
- dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
178563
- rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust"))
178564
- };
179496
+ function scanOptionsFor(codeInclude, language) {
179497
+ const visibility = codeInclude[language]?.visibility;
179498
+ return visibility !== undefined ? { visibility } : {};
179499
+ }
179500
+ function workerRequest(language, files, options, context, adapterOptions) {
179501
+ return {
179502
+ schemaVersion: 1,
179503
+ requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
179504
+ language,
179505
+ projectRoot: context.projectRoot,
179506
+ files: files.map(({ filePath, content }) => ({ filePath, content })),
179507
+ options
179508
+ };
179509
+ }
179510
+ function failedBatch(language, files, diagnostic) {
179511
+ return files.map(({ filePath }) => ({
179512
+ ...emptyScan(language, filePath),
179513
+ diagnostics: [{ ...diagnostic, target: filePath }]
179514
+ }));
179515
+ }
178565
179516
  function emptyScan(language, filePath) {
178566
179517
  return {
178567
179518
  language,
@@ -178572,11 +179523,16 @@ function emptyScan(language, filePath) {
178572
179523
  diagnostics: []
178573
179524
  };
178574
179525
  }
178575
- function fileScopedScannerDiagnostic(diagnostic, filePath) {
178576
- return { ...diagnostic, target: filePath };
178577
- }
178578
- function normalizeScannerCommand(value) {
178579
- return Array.isArray(value) ? { ok: true, command: value } : value;
179526
+ function workerCommand(value) {
179527
+ if (Array.isArray(value)) {
179528
+ return { ok: true, command: value, stripEnv: [], runtime: [] };
179529
+ }
179530
+ return value.ok ? {
179531
+ ok: true,
179532
+ command: value.command,
179533
+ stripEnv: value.stripEnv ?? [],
179534
+ runtime: value.runtime ?? []
179535
+ } : value;
178580
179536
  }
178581
179537
 
178582
179538
  // src/scan/markdown/markdown.ts
@@ -178811,6 +179767,42 @@ function flushDanglingWith(pending, diagnostics, message) {
178811
179767
 
178812
179768
  // src/query/project-scan.ts
178813
179769
  function scanProject(options) {
179770
+ const loaded = loadProjectScan(options, options.keepContent === true);
179771
+ if (!loaded.ok) {
179772
+ return loaded;
179773
+ }
179774
+ const { inputs } = loaded;
179775
+ const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, codeScanOptions(inputs, options));
179776
+ const docReads = readDocFiles(inputs, options);
179777
+ return { ok: true, scan: finishProjectScan(inputs, codeScan, docReads, options.buildGraph) };
179778
+ }
179779
+ function scanProjectAsync(options) {
179780
+ const loaded = loadProjectScan(options, true);
179781
+ if (!loaded.ok) {
179782
+ return cancelableSequence(async () => loaded);
179783
+ }
179784
+ const { inputs } = loaded;
179785
+ const fingerprint = JSON.stringify(inputs.config);
179786
+ const sameConfiguration = options.cache.fingerprint === fingerprint;
179787
+ if (!sameConfiguration && options.cache.fingerprint !== "") {
179788
+ options.onConfigurationChange?.();
179789
+ }
179790
+ const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179791
+ ...codeScanOptions(inputs, options),
179792
+ ...sameConfiguration ? { cache: options.cache.entries } : {}
179793
+ });
179794
+ const docReads = readDocFiles(inputs, options);
179795
+ return cancelableSequence(async (step) => {
179796
+ const { cache, ...code } = await step(codeScan);
179797
+ const scan = finishProjectScan(inputs, code, docReads, true);
179798
+ return {
179799
+ ok: true,
179800
+ scan,
179801
+ cache: { fingerprint, entries: cache }
179802
+ };
179803
+ });
179804
+ }
179805
+ function loadProjectScan(options, keepContent) {
178814
179806
  const configResult = loadConfig(options.projectRoot);
178815
179807
  if (!configResult.ok) {
178816
179808
  return { ok: false, diagnostics: configResult.diagnostics };
@@ -178820,32 +179812,51 @@ function scanProject(options) {
178820
179812
  return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
178821
179813
  }
178822
179814
  const collectCode = options.collectCode ?? collectCodeFiles;
178823
- const collectDocs = options.collectDocs ?? collectFiles;
178824
- const readFile = options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath));
178825
- const diagnostics = [
178826
- ...configResult.diagnostics,
178827
- ...manifestResult.diagnostics
178828
- ];
178829
- const contentByFile = options.keepContent ? new Map : undefined;
178830
- const codeScan = scanCodeFiles(options.projectRoot, collectCode(options.projectRoot, configResult.config.include.code), configResult.config.include.code, readFile, {
179815
+ return {
179816
+ ok: true,
179817
+ inputs: {
179818
+ config: configResult.config,
179819
+ manifest: manifestResult.manifest,
179820
+ diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics],
179821
+ codeFiles: collectCode(options.projectRoot, configResult.config.include.code),
179822
+ readFile: options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath)),
179823
+ contentByFile: keepContent ? new Map : undefined
179824
+ }
179825
+ };
179826
+ }
179827
+ function codeScanOptions(inputs, options) {
179828
+ const { contentByFile } = inputs;
179829
+ const { scanners } = inputs.config;
179830
+ return {
178831
179831
  ...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
178832
- ...options.adapters === undefined ? {} : { adapters: options.adapters }
179832
+ ...options.adapters === undefined ? {} : { adapters: options.adapters },
179833
+ ...scanners === undefined ? {} : { scanners }
179834
+ };
179835
+ }
179836
+ function readDocFiles(inputs, options) {
179837
+ const collectDocs = options.collectDocs ?? collectFiles;
179838
+ return collectDocs(options.projectRoot, inputs.config.include.docs).map((relPath) => {
179839
+ const read = inputs.readFile(relPath);
179840
+ if (read.ok) {
179841
+ inputs.contentByFile?.set(relPath, read.content);
179842
+ }
179843
+ return { relPath, read };
178833
179844
  });
178834
- diagnostics.push(...codeScan.diagnostics);
179845
+ }
179846
+ function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
179847
+ const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
178835
179848
  const docFiles = [];
178836
- for (const relPath of collectDocs(options.projectRoot, configResult.config.include.docs)) {
178837
- const read = readFile(relPath);
179849
+ for (const { relPath, read } of docReads) {
178838
179850
  if (!read.ok) {
178839
179851
  diagnostics.push(read.diagnostic);
178840
179852
  continue;
178841
179853
  }
178842
- contentByFile?.set(relPath, read.content);
178843
179854
  const scan2 = scanMarkdown(relPath, read.content);
178844
179855
  diagnostics.push(...scan2.diagnostics);
178845
179856
  docFiles.push(scan2);
178846
179857
  }
178847
179858
  const applied = applyLinkManifest({
178848
- manifest: manifestResult.manifest,
179859
+ manifest: inputs.manifest,
178849
179860
  codeFiles: codeScan.codeFiles,
178850
179861
  docFiles,
178851
179862
  scanDiagnostics: diagnostics
@@ -178857,25 +179868,26 @@ function scanProject(options) {
178857
179868
  docFiles: applied.docFiles,
178858
179869
  diagnostics
178859
179870
  };
178860
- if (contentByFile !== undefined) {
178861
- scan.contentByFile = contentByFile;
179871
+ if (inputs.contentByFile !== undefined) {
179872
+ scan.contentByFile = inputs.contentByFile;
178862
179873
  }
178863
- if (options.buildGraph === true) {
179874
+ if (buildGraph === true) {
178864
179875
  scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
178865
179876
  }
178866
- return {
178867
- ok: true,
178868
- scan
178869
- };
179877
+ return scan;
178870
179878
  }
178871
179879
 
178872
179880
  // src/lsp/project.ts
178873
179881
  class Project {
178874
179882
  projectRoot;
179883
+ options;
178875
179884
  overlay = new Map;
179885
+ overlayRevision = 0;
178876
179886
  current = emptyState();
178877
- constructor(projectRoot) {
179887
+ cache = emptyCodeScanCache();
179888
+ constructor(projectRoot, options = {}) {
178878
179889
  this.projectRoot = projectRoot;
179890
+ this.options = options;
178879
179891
  }
178880
179892
  get root() {
178881
179893
  return this.projectRoot;
@@ -178885,48 +179897,57 @@ class Project {
178885
179897
  }
178886
179898
  setOverlay(relPath, content) {
178887
179899
  this.overlay.set(relPath, content);
179900
+ this.overlayRevision += 1;
178888
179901
  }
178889
179902
  clearOverlay(relPath) {
178890
179903
  this.overlay.delete(relPath);
179904
+ this.overlayRevision += 1;
178891
179905
  }
178892
179906
  resolve() {
178893
179907
  const outcome = scanProject({
178894
- projectRoot: this.projectRoot,
178895
- collectCode: (_projectRoot, include) => this.collectCode(include),
178896
- collectDocs: (_projectRoot, patterns) => this.collect(patterns, false),
178897
- readFile: (relPath) => this.readContent(relPath),
179908
+ ...this.scanSources(this.overlay),
178898
179909
  buildGraph: true,
178899
179910
  keepContent: true
178900
179911
  });
178901
- if (!outcome.ok) {
178902
- this.current = {
178903
- ...emptyState(),
178904
- diagnostics: sortDiagnostics(outcome.diagnostics)
178905
- };
179912
+ this.current = resolvedState(outcome);
179913
+ return this.current;
179914
+ }
179915
+ resolveAsync() {
179916
+ const revision = this.overlayRevision;
179917
+ const scan = scanProjectAsync({
179918
+ ...this.scanSources(new Map(this.overlay)),
179919
+ cache: this.cache,
179920
+ onConfigurationChange: clearRuntimeProbeCache
179921
+ });
179922
+ let cancelled = false;
179923
+ const promise = scan.promise.then((outcome) => {
179924
+ if (cancelled || revision !== this.overlayRevision) {
179925
+ throw abortError();
179926
+ }
179927
+ if (outcome.ok) {
179928
+ this.cache = outcome.cache;
179929
+ }
179930
+ this.current = resolvedState(outcome);
178906
179931
  return this.current;
178907
- }
178908
- const {
178909
- codeFiles,
178910
- docFiles,
178911
- diagnostics: scanDiagnostics,
178912
- contentByFile,
178913
- graph
178914
- } = outcome.scan;
178915
- const relationship = resolveLinks({
178916
- codeFiles,
178917
- docFiles,
178918
- scanDiagnostics,
178919
- audit: false
178920
179932
  });
178921
- this.current = {
178922
- graph,
178923
- index: buildPositionIndex(graph),
178924
- diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
178925
- contentByFile
179933
+ return {
179934
+ promise,
179935
+ cancel() {
179936
+ cancelled = true;
179937
+ scan.cancel();
179938
+ }
179939
+ };
179940
+ }
179941
+ scanSources(overlay) {
179942
+ return {
179943
+ projectRoot: this.projectRoot,
179944
+ collectCode: (_projectRoot, include) => this.collectCode(include, overlay),
179945
+ collectDocs: (_projectRoot, patterns) => this.collectDocs(patterns, overlay),
179946
+ readFile: (relPath) => this.readContent(relPath, overlay),
179947
+ ...this.options.adapters === undefined ? {} : { adapters: this.options.adapters }
178926
179948
  };
178927
- return this.current;
178928
179949
  }
178929
- collectCode(codeInclude) {
179950
+ collectCode(codeInclude, overlay) {
178930
179951
  const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
178931
179952
  const seen = new Set(onDisk.map((file) => file.relPath));
178932
179953
  const all = [...onDisk];
@@ -178935,11 +179956,11 @@ class Project {
178935
179956
  if (entry === undefined) {
178936
179957
  continue;
178937
179958
  }
178938
- for (const relPath of this.overlay.keys()) {
179959
+ for (const relPath of overlay.keys()) {
178939
179960
  if (seen.has(relPath)) {
178940
179961
  continue;
178941
179962
  }
178942
- if (language === "typescript" && relPath.endsWith(".d.ts")) {
179963
+ if (hasExcludedSuffix(language, relPath)) {
178943
179964
  continue;
178944
179965
  }
178945
179966
  if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
@@ -178950,22 +179971,19 @@ class Project {
178950
179971
  }
178951
179972
  return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
178952
179973
  }
178953
- readContent(relPath) {
178954
- const overlaid = this.overlay.get(relPath);
179974
+ readContent(relPath, overlay) {
179975
+ const overlaid = overlay.get(relPath);
178955
179976
  if (overlaid !== undefined) {
178956
179977
  return { ok: true, content: overlaid };
178957
179978
  }
178958
179979
  return readManagedFile(this.projectRoot, relPath);
178959
179980
  }
178960
- collect(patterns, isCode) {
179981
+ collectDocs(patterns, overlay) {
178961
179982
  const paths = new Set(collectFiles(this.projectRoot, patterns));
178962
- for (const relPath of this.overlay.keys()) {
179983
+ for (const relPath of overlay.keys()) {
178963
179984
  if (paths.has(relPath)) {
178964
179985
  continue;
178965
179986
  }
178966
- if (isCode && relPath.endsWith(".d.ts")) {
178967
- continue;
178968
- }
178969
179987
  if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
178970
179988
  paths.add(relPath);
178971
179989
  }
@@ -178973,6 +179991,19 @@ class Project {
178973
179991
  return [...paths].toSorted(comparePaths);
178974
179992
  }
178975
179993
  }
179994
+ function resolvedState(outcome) {
179995
+ if (!outcome.ok) {
179996
+ return { ...emptyState(), diagnostics: sortDiagnostics(outcome.diagnostics) };
179997
+ }
179998
+ const { codeFiles, docFiles, diagnostics: scanDiagnostics, contentByFile, graph } = outcome.scan;
179999
+ const relationship = resolveLinks({ codeFiles, docFiles, scanDiagnostics, audit: false });
180000
+ return {
180001
+ graph,
180002
+ index: buildPositionIndex(graph),
180003
+ diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
180004
+ contentByFile
180005
+ };
180006
+ }
178976
180007
  function emptyState() {
178977
180008
  const graph = buildLinkGraph([], []);
178978
180009
  return {
@@ -178983,6 +180014,74 @@ function emptyState() {
178983
180014
  };
178984
180015
  }
178985
180016
 
180017
+ // src/lsp/scheduler.ts
180018
+ class RescanScheduler {
180019
+ options;
180020
+ generation = 0;
180021
+ dirty = false;
180022
+ debounce;
180023
+ active;
180024
+ stopped = false;
180025
+ constructor(options) {
180026
+ this.options = options;
180027
+ }
180028
+ request(when) {
180029
+ if (this.stopped) {
180030
+ return;
180031
+ }
180032
+ this.generation += 1;
180033
+ this.dirty = true;
180034
+ this.active?.task.cancel();
180035
+ this.clearDebounce();
180036
+ if (when === "debounced") {
180037
+ const handle = this.options.timers.setTimeout(() => {
180038
+ this.debounce = undefined;
180039
+ this.startIfReady();
180040
+ }, this.options.debounceMs);
180041
+ this.debounce = { handle };
180042
+ return;
180043
+ }
180044
+ this.startIfReady();
180045
+ }
180046
+ stop() {
180047
+ this.stopped = true;
180048
+ this.clearDebounce();
180049
+ this.active?.task.cancel();
180050
+ }
180051
+ startIfReady() {
180052
+ if (this.stopped || !this.dirty || this.active !== undefined || this.debounce !== undefined) {
180053
+ return;
180054
+ }
180055
+ this.dirty = false;
180056
+ const active = { generation: this.generation, task: this.options.scan() };
180057
+ this.active = active;
180058
+ active.task.promise.then((state) => this.settle(active, () => this.accept(active, state)), (error) => this.settle(active, () => this.fail(error)));
180059
+ }
180060
+ settle(active, report) {
180061
+ if (this.active === active) {
180062
+ this.active = undefined;
180063
+ }
180064
+ report();
180065
+ this.startIfReady();
180066
+ }
180067
+ accept(active, state) {
180068
+ if (!this.stopped && active.generation === this.generation) {
180069
+ this.options.onState(state);
180070
+ }
180071
+ }
180072
+ fail(error) {
180073
+ if (!isAbortError(error)) {
180074
+ this.options.onError(error);
180075
+ }
180076
+ }
180077
+ clearDebounce() {
180078
+ if (this.debounce !== undefined) {
180079
+ this.options.timers.clearTimeout(this.debounce.handle);
180080
+ this.debounce = undefined;
180081
+ }
180082
+ }
180083
+ }
180084
+
178986
180085
  // src/lsp/transport.ts
178987
180086
  var HEADER_SEPARATOR = `\r
178988
180087
  \r
@@ -179050,6 +180149,10 @@ function parseContentLength(headerText) {
179050
180149
  }
179051
180150
 
179052
180151
  // src/lsp/server.ts
180152
+ var GLOBAL_TIMERS = {
180153
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
180154
+ clearTimeout: (handle) => clearTimeout(handle)
180155
+ };
179053
180156
  var CAPABILITIES = {
179054
180157
  textDocumentSync: 1,
179055
180158
  hoverProvider: true,
@@ -179060,44 +180163,55 @@ var CAPABILITIES = {
179060
180163
  class Server {
179061
180164
  send;
179062
180165
  project = null;
180166
+ scheduler = null;
179063
180167
  openFiles = new Set;
179064
- dirty = true;
179065
- timer = null;
179066
180168
  shuttingDown = false;
179067
180169
  makeProject;
179068
180170
  debounceMs;
179069
180171
  onExit;
180172
+ timers;
180173
+ onScanError;
179070
180174
  constructor(send, options = {}) {
179071
180175
  this.send = send;
179072
180176
  this.makeProject = options.makeProject ?? ((root) => new Project(root));
179073
180177
  this.debounceMs = options.debounceMs ?? 50;
179074
180178
  this.onExit = options.onExit ?? ((code) => process.exit(code));
180179
+ this.timers = options.timers ?? GLOBAL_TIMERS;
180180
+ this.onScanError = options.onScanError ?? ((error) => process.stderr.write(`docbridge lsp: scan failed: ${reasonOf(error)}
180181
+ `));
179075
180182
  }
179076
180183
  handle(message) {
179077
180184
  switch (message.method) {
179078
180185
  case "initialize":
179079
180186
  this.respond(message.id, { capabilities: CAPABILITIES });
179080
- this.project = this.makeProject(resolveRoot(message.params));
180187
+ this.start(this.makeProject(resolveRoot(message.params)));
179081
180188
  break;
179082
180189
  case "initialized":
179083
- this.flush();
180190
+ this.scheduler?.request("now");
179084
180191
  break;
179085
180192
  case "shutdown":
179086
180193
  this.shuttingDown = true;
179087
- this.clearTimer();
180194
+ this.scheduler?.stop();
179088
180195
  this.respond(message.id, null);
179089
180196
  break;
179090
180197
  case "exit":
180198
+ this.scheduler?.stop();
179091
180199
  this.onExit(this.shuttingDown ? 0 : 1);
179092
180200
  break;
179093
180201
  case "textDocument/didOpen":
179094
- this.onDidOpen(message.params);
180202
+ if (!this.shuttingDown) {
180203
+ this.onDidOpen(message.params);
180204
+ }
179095
180205
  break;
179096
180206
  case "textDocument/didChange":
179097
- this.onDidChange(message.params);
180207
+ if (!this.shuttingDown) {
180208
+ this.onDidChange(message.params);
180209
+ }
179098
180210
  break;
179099
180211
  case "textDocument/didClose":
179100
- this.onDidClose(message.params);
180212
+ if (!this.shuttingDown) {
180213
+ this.onDidClose(message.params);
180214
+ }
179101
180215
  break;
179102
180216
  case "textDocument/hover":
179103
180217
  this.respond(message.id, this.onHover(message.params));
@@ -179114,6 +180228,16 @@ class Server {
179114
180228
  }
179115
180229
  }
179116
180230
  }
180231
+ start(project) {
180232
+ this.project = project;
180233
+ this.scheduler = new RescanScheduler({
180234
+ scan: () => project.resolveAsync(),
180235
+ onState: (state) => this.publishOpenFiles(state),
180236
+ onError: this.onScanError,
180237
+ debounceMs: this.debounceMs,
180238
+ timers: this.timers
180239
+ });
180240
+ }
179117
180241
  onDidOpen(params) {
179118
180242
  const doc = textDocument(params);
179119
180243
  const rel = this.relPath(doc?.uri);
@@ -179123,8 +180247,7 @@ class Server {
179123
180247
  }
179124
180248
  this.openFiles.add(rel);
179125
180249
  this.project.setOverlay(rel, text);
179126
- this.dirty = true;
179127
- this.flush();
180250
+ this.scheduler?.request("now");
179128
180251
  }
179129
180252
  onDidChange(params) {
179130
180253
  const record = params;
@@ -179135,7 +180258,7 @@ class Server {
179135
180258
  return;
179136
180259
  }
179137
180260
  this.project.setOverlay(rel, text);
179138
- this.scheduleFlush();
180261
+ this.scheduler?.request("debounced");
179139
180262
  }
179140
180263
  onDidClose(params) {
179141
180264
  const doc = textDocument(params);
@@ -179145,9 +180268,8 @@ class Server {
179145
180268
  }
179146
180269
  this.openFiles.delete(rel);
179147
180270
  this.project.clearOverlay(rel);
179148
- this.dirty = true;
179149
180271
  this.publish(rel, []);
179150
- this.flush();
180272
+ this.scheduler?.request("now");
179151
180273
  }
179152
180274
  onHover(params) {
179153
180275
  const located = this.locate(params);
@@ -179186,7 +180308,6 @@ class Server {
179186
180308
  if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
179187
180309
  return null;
179188
180310
  }
179189
- this.flush();
179190
180311
  return {
179191
180312
  state: this.project.state,
179192
180313
  rel,
@@ -179194,28 +180315,14 @@ class Server {
179194
180315
  position: fromLspPosition({ line: position.line, character: position.character })
179195
180316
  };
179196
180317
  }
179197
- scheduleFlush() {
179198
- this.dirty = true;
179199
- this.clearTimer();
179200
- this.timer = setTimeout(() => {
179201
- this.timer = null;
179202
- this.flush();
179203
- }, this.debounceMs);
179204
- }
179205
- flush() {
179206
- this.clearTimer();
179207
- if (this.project === null || !this.dirty) {
179208
- return;
179209
- }
179210
- this.dirty = false;
179211
- const state = this.project.resolve();
180318
+ publishOpenFiles(state) {
179212
180319
  for (const rel of this.openFiles) {
179213
180320
  const content = state.contentByFile.get(rel) ?? "";
179214
180321
  this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
179215
180322
  }
179216
180323
  }
179217
180324
  publish(rel, diagnostics) {
179218
- if (this.project === null) {
180325
+ if (this.project === null || this.shuttingDown) {
179219
180326
  return;
179220
180327
  }
179221
180328
  this.send({
@@ -179236,12 +180343,6 @@ class Server {
179236
180343
  }
179237
180344
  this.send({ jsonrpc: "2.0", id, result });
179238
180345
  }
179239
- clearTimer() {
179240
- if (this.timer !== null) {
179241
- clearTimeout(this.timer);
179242
- this.timer = null;
179243
- }
179244
- }
179245
180346
  }
179246
180347
  function resolveRoot(params) {
179247
180348
  const record = params;
@@ -179335,7 +180436,7 @@ function check(options) {
179335
180436
  }
179336
180437
 
179337
180438
  // src/query/related.ts
179338
- import { isAbsolute as isAbsolute2, relative as relative2 } from "node:path";
180439
+ import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
179339
180440
  function normalizeChangedPaths(projectRoot, paths) {
179340
180441
  const normalized = new Set;
179341
180442
  for (const raw of paths) {
@@ -179343,7 +180444,7 @@ function normalizeChangedPaths(projectRoot, paths) {
179343
180444
  if (trimmed === "") {
179344
180445
  continue;
179345
180446
  }
179346
- const relativized = isAbsolute2(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
180447
+ const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
179347
180448
  normalized.add(relativized.replace(/^(\.\/)+/, ""));
179348
180449
  }
179349
180450
  return [...normalized];
@@ -179765,7 +180866,7 @@ function comparePairs(left, right) {
179765
180866
  // src/setup/update-cache.ts
179766
180867
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
179767
180868
  import { homedir } from "node:os";
179768
- import { dirname as dirname3, join as join7 } from "node:path";
180869
+ import { dirname as dirname3, join as join8 } from "node:path";
179769
180870
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
179770
180871
  var UPDATE_CACHE_SCHEMA_VERSION = 1;
179771
180872
  function resolveUpdateCachePath(env = process.env, home = homedir()) {
@@ -179773,8 +180874,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
179773
180874
  if (explicit !== undefined && explicit.length > 0) {
179774
180875
  return explicit;
179775
180876
  }
179776
- const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join7(home, ".cache");
179777
- return join7(cacheRoot, "docbridge", "update-check.json");
180877
+ const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join8(home, ".cache");
180878
+ return join8(cacheRoot, "docbridge", "update-check.json");
179778
180879
  }
179779
180880
  function readUpdateCache(path) {
179780
180881
  let rawText;
@@ -180018,7 +181119,7 @@ function isEnabledFlag(value) {
180018
181119
 
180019
181120
  // src/cli/docs.ts
180020
181121
  import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
180021
- import { join as join8 } from "node:path";
181122
+ import { join as join9 } from "node:path";
180022
181123
 
180023
181124
  // src/cli/errors.ts
180024
181125
  class CliError extends Error {
@@ -180189,7 +181290,7 @@ function parseDocument(name, source) {
180189
181290
  };
180190
181291
  }
180191
181292
  function createFileDocumentationReader(packageRoot) {
180192
- const docsRoot = join8(packageRoot, "docs", "user");
181293
+ const docsRoot = join9(packageRoot, "docs", "user");
180193
181294
  function documents() {
180194
181295
  let fileNames;
180195
181296
  try {
@@ -180202,7 +181303,7 @@ function createFileDocumentationReader(packageRoot) {
180202
181303
  }
180203
181304
  return fileNames.map((fileName) => {
180204
181305
  const name = fileName.slice(0, -".md".length);
180205
- return parseDocument(name, readFileSync5(join8(docsRoot, fileName), "utf8"));
181306
+ return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
180206
181307
  });
180207
181308
  }
180208
181309
  return {
@@ -180599,12 +181700,12 @@ function hasHelpFlag(args) {
180599
181700
  }
180600
181701
 
180601
181702
  // src/cli/init.ts
180602
- import { readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
180603
- import { join as join12, resolve as resolve2 } from "node:path";
181703
+ import { readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
181704
+ import { join as join13, resolve as resolve3 } from "node:path";
180604
181705
 
180605
181706
  // src/setup/init-discovery.ts
180606
- import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
180607
- import { basename, dirname as dirname4, join as join9 } from "node:path";
181707
+ import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
181708
+ import { basename, dirname as dirname4, join as join10 } from "node:path";
180608
181709
  var HIGH_CONFIDENCE_DIR_NAMES = new Set([
180609
181710
  "docs/specs",
180610
181711
  "specs",
@@ -180634,20 +181735,39 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
180634
181735
  "contributing"
180635
181736
  ]);
180636
181737
  var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
180637
- var TYPESCRIPT_PATTERNS = [
180638
- "src/**/*.ts",
180639
- "lib/**/*.ts",
180640
- "packages/*/src/**/*.ts",
180641
- "apps/*/src/**/*.ts"
180642
- ];
181738
+ var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
181739
+ var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
181740
+ var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
180643
181741
  var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
180644
181742
  var DART_PATTERNS = ["lib/**/*.dart"];
180645
181743
  var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
181744
+ var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
181745
+ var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
180646
181746
  var LANGUAGE_PATTERNS = {
180647
181747
  typescript: TYPESCRIPT_PATTERNS,
180648
181748
  swift: SWIFT_PATTERNS,
180649
181749
  dart: DART_PATTERNS,
180650
- rust: RUST_PATTERNS
181750
+ rust: RUST_PATTERNS,
181751
+ go: GO_PATTERNS,
181752
+ javascript: JAVASCRIPT_PATTERNS,
181753
+ ruby: RUBY_PATTERNS
181754
+ };
181755
+ var EXCLUDED_CODE_SEGMENTS = {
181756
+ typescript: ["__tests__", "tests", "test"],
181757
+ swift: ["Tests", "tests"],
181758
+ dart: ["test"],
181759
+ rust: ["target", "tests", "benches", "examples"],
181760
+ go: ["vendor", "testdata"],
181761
+ javascript: ["__tests__", "tests", "test"],
181762
+ python: ["tests", "venv", "dist", "site-packages"],
181763
+ ruby: ["spec", "test", "vendor"]
181764
+ };
181765
+ var TEST_FILE_PATTERNS = {
181766
+ typescript: /\.(?:test|spec)\.[cm]?tsx?$/u,
181767
+ swift: /tests\.swift$/u,
181768
+ dart: /_test\.dart$/u,
181769
+ go: /_test\.go$/u,
181770
+ javascript: /\.(?:test|spec)\.[cm]?jsx?$/u
180651
181771
  };
180652
181772
  function discoverRepository(projectRoot) {
180653
181773
  return {
@@ -180731,8 +181851,8 @@ function discoverCodeScope(projectRoot) {
180731
181851
  };
180732
181852
  }
180733
181853
  function discoverAgentTarget(projectRoot) {
180734
- const hasAgentsDir = existsSync3(join9(projectRoot, ".agents"));
180735
- const hasClaudeDir = existsSync3(join9(projectRoot, ".claude"));
181854
+ const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
181855
+ const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
180736
181856
  let defaultTarget = "none";
180737
181857
  let recommendedTarget = "none";
180738
181858
  let message;
@@ -180759,7 +181879,7 @@ function discoverAgentTarget(projectRoot) {
180759
181879
  };
180760
181880
  }
180761
181881
  function collectMarkdownFiles(projectRoot, currentDir = ".") {
180762
- const absoluteDir = join9(projectRoot, currentDir);
181882
+ const absoluteDir = join10(projectRoot, currentDir);
180763
181883
  let entries;
180764
181884
  try {
180765
181885
  entries = readdirSync3(absoluteDir);
@@ -180773,10 +181893,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
180773
181893
  if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
180774
181894
  continue;
180775
181895
  }
180776
- const absolutePath = join9(projectRoot, relPath);
181896
+ const absolutePath = join10(projectRoot, relPath);
180777
181897
  let stats;
180778
181898
  try {
180779
- stats = statSync2(absolutePath);
181899
+ stats = statSync3(absolutePath);
180780
181900
  } catch {
180781
181901
  continue;
180782
181902
  }
@@ -180814,8 +181934,22 @@ function scoreDocsDirectory(directory) {
180814
181934
  }
180815
181935
  return score;
180816
181936
  }
181937
+ function scriptPatterns(language) {
181938
+ return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
181939
+ }
180817
181940
  function activeCodePatterns(projectRoot, language) {
180818
- return LANGUAGE_PATTERNS[language].filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
181941
+ const candidates = language === "python" ? pythonPatterns(projectRoot) : LANGUAGE_PATTERNS[language];
181942
+ return candidates.filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
181943
+ }
181944
+ function pythonPatterns(projectRoot) {
181945
+ let entries;
181946
+ try {
181947
+ entries = readdirSync3(projectRoot, { withFileTypes: true });
181948
+ } catch {
181949
+ return [];
181950
+ }
181951
+ 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();
181952
+ return ["src/**/*.py", ...packages.map((name) => `${name}/**/*.py`)];
180819
181953
  }
180820
181954
  function countCodeFiles(projectRoot, patterns, language) {
180821
181955
  const seen = new Set;
@@ -180831,28 +181965,8 @@ function countCodeFiles(projectRoot, patterns, language) {
180831
181965
  function isExcludedCodeFile(filePath, language) {
180832
181966
  const lower = filePath.toLowerCase();
180833
181967
  const segments = filePath.split("/");
180834
- if (language === "typescript") {
180835
- if (lower.endsWith(".d.ts")) {
180836
- return true;
180837
- }
180838
- if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
180839
- return true;
180840
- }
180841
- }
180842
- if (language === "swift") {
180843
- if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
180844
- return true;
180845
- }
180846
- }
180847
- if (language === "dart") {
180848
- if (lower.endsWith("_test.dart") || segments.includes("test")) {
180849
- return true;
180850
- }
180851
- }
180852
- if (language === "rust") {
180853
- if (segments.includes("target") || segments.includes("tests") || segments.includes("benches") || segments.includes("examples")) {
180854
- return true;
180855
- }
181968
+ if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((segment) => segments.includes(segment))) {
181969
+ return true;
180856
181970
  }
180857
181971
  if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
180858
181972
  return true;
@@ -180861,17 +181975,17 @@ function isExcludedCodeFile(filePath, language) {
180861
181975
  }
180862
181976
 
180863
181977
  // src/setup/init-plan.ts
180864
- import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
180865
- import { join as join11, relative as relative3 } from "node:path";
181978
+ import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
181979
+ import { join as join12, relative as relative3 } from "node:path";
180866
181980
 
180867
181981
  // src/setup/skill-assets.ts
180868
181982
  import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
180869
- import { join as join10 } from "node:path";
181983
+ import { join as join11 } from "node:path";
180870
181984
  function classifyManagedPath(projectRoot, relativePath) {
180871
181985
  const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
180872
181986
  let current = projectRoot;
180873
181987
  for (const [index, segment] of segments.entries()) {
180874
- current = join10(current, segment);
181988
+ current = join11(current, segment);
180875
181989
  const isLast = index === segments.length - 1;
180876
181990
  let stats;
180877
181991
  try {
@@ -180918,7 +182032,7 @@ function compareSkillTree(installedDir, templateDir) {
180918
182032
  continue;
180919
182033
  }
180920
182034
  installedFiles.delete(file);
180921
- if (readFileOrUndefined(join10(installedDir, file)) !== readFileOrUndefined(join10(templateDir, file))) {
182035
+ if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
180922
182036
  modified.push(file);
180923
182037
  }
180924
182038
  }
@@ -180929,7 +182043,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180929
182043
  return;
180930
182044
  }
180931
182045
  const kind = classifyManagedPath(projectRoot, operation.path);
180932
- const destinationDir = join10(projectRoot, operation.path);
182046
+ const destinationDir = join11(projectRoot, operation.path);
180933
182047
  if (operation.action === "remove") {
180934
182048
  if (kind === "directory") {
180935
182049
  rmSync(destinationDir, { recursive: true, force: true });
@@ -180947,7 +182061,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180947
182061
  rmSync(destinationDir, { recursive: true, force: true });
180948
182062
  }
180949
182063
  mkdirSync3(destinationDir, { recursive: true });
180950
- cpSync(join10(packageRoot, "templates", "skills", skillName), destinationDir, {
182064
+ cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
180951
182065
  recursive: true,
180952
182066
  force: true
180953
182067
  });
@@ -180955,7 +182069,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180955
182069
  function collectSkillFiles(root, prefix, files) {
180956
182070
  let entries;
180957
182071
  try {
180958
- entries = readdirSync4(join10(root, prefix), { withFileTypes: true });
182072
+ entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
180959
182073
  } catch {
180960
182074
  return;
180961
182075
  }
@@ -181066,14 +182180,14 @@ function buildConfigFromScope(scope) {
181066
182180
  };
181067
182181
  }
181068
182182
  function listDistributableSkills(packageRoot) {
181069
- const skillsRoot = join11(packageRoot, "templates", "skills");
182183
+ const skillsRoot = join12(packageRoot, "templates", "skills");
181070
182184
  let entries;
181071
182185
  try {
181072
182186
  entries = readdirSync5(skillsRoot);
181073
182187
  } catch {
181074
182188
  return [];
181075
182189
  }
181076
- return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync4(join11(skillsRoot, entry, "SKILL.md"))).toSorted();
182190
+ return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync5(join12(skillsRoot, entry, "SKILL.md"))).toSorted();
181077
182191
  }
181078
182192
  function formatInitPlan(plan) {
181079
182193
  const lines = [];
@@ -181178,7 +182292,7 @@ function planSkillOperations(input) {
181178
182292
  if (!availableSkills.has(skillName)) {
181179
182293
  continue;
181180
182294
  }
181181
- const destinationDir = join11(input.projectRoot, destination, skillName);
182295
+ const destinationDir = join12(input.projectRoot, destination, skillName);
181182
182296
  const path = relative3(input.projectRoot, destinationDir);
181183
182297
  const kind = classifyManagedPath(input.projectRoot, path);
181184
182298
  if (kind !== "absent" && kind !== "directory") {
@@ -181217,7 +182331,7 @@ function planLegacySkillOperations(input) {
181217
182331
  const operations = [];
181218
182332
  const leftover = [];
181219
182333
  for (const skillName of LEGACY_SKILL_NAMES) {
181220
- const destinationDir = join11(input.projectRoot, input.destination, skillName);
182334
+ const destinationDir = join12(input.projectRoot, input.destination, skillName);
181221
182335
  const path = relative3(input.projectRoot, destinationDir);
181222
182336
  const kind = classifyManagedPath(input.projectRoot, path);
181223
182337
  if (kind === "absent") {
@@ -181297,7 +182411,7 @@ function resolveInitWithAgentTarget(discovery, options) {
181297
182411
  return { target: discovery.agent.defaultTarget, error: undefined };
181298
182412
  }
181299
182413
  function readExistingConfig(projectRoot) {
181300
- const configPath = join11(projectRoot, CONFIG_FILE_NAME2);
182414
+ const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
181301
182415
  let rawText;
181302
182416
  try {
181303
182417
  rawText = readFileSync7(configPath, "utf8");
@@ -181548,7 +182662,7 @@ function executePlan(projectRoot, plan, packageRoot) {
181548
182662
  }
181549
182663
  }
181550
182664
  function executeFileOperation(projectRoot, operation) {
181551
- const absolutePath = join12(projectRoot, operation.path);
182665
+ const absolutePath = join13(projectRoot, operation.path);
181552
182666
  if (operation.action === "create" && operation.content !== undefined) {
181553
182667
  writeFileSync2(absolutePath, operation.content, "utf8");
181554
182668
  }
@@ -181557,10 +182671,10 @@ function executeFileOperation(projectRoot, operation) {
181557
182671
  }
181558
182672
  }
181559
182673
  function resolveProjectRoot(root, command) {
181560
- const projectRoot = resolve2(root);
182674
+ const projectRoot = resolve3(root);
181561
182675
  let stats;
181562
182676
  try {
181563
- stats = statSync3(projectRoot);
182677
+ stats = statSync4(projectRoot);
181564
182678
  } catch {
181565
182679
  throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
181566
182680
  }
@@ -181595,21 +182709,12 @@ ${block.content}`;
181595
182709
  const fence = codeFence(block.content);
181596
182710
  return `${header}
181597
182711
 
181598
- ${fence}${fenceLanguage(block.language)}
182712
+ ${fence}${fenceLanguage(block)}
181599
182713
  ${block.content}
181600
182714
  ${fence}`;
181601
182715
  }
181602
- function fenceLanguage(language) {
181603
- if (language === "swift") {
181604
- return "swift";
181605
- }
181606
- if (language === "dart") {
181607
- return "dart";
181608
- }
181609
- if (language === "rust") {
181610
- return "rust";
181611
- }
181612
- return "ts";
182716
+ function fenceLanguage(block) {
182717
+ return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
181613
182718
  }
181614
182719
  function codeFence(content) {
181615
182720
  let longestRun = 0;
@@ -181741,12 +182846,12 @@ function formatRelatedSummary(summary) {
181741
182846
  }
181742
182847
 
181743
182848
  // src/cli/upgrade.ts
181744
- import { statSync as statSync4 } from "node:fs";
181745
- import { resolve as resolve3 } from "node:path";
182849
+ import { statSync as statSync5 } from "node:fs";
182850
+ import { resolve as resolve4 } from "node:path";
181746
182851
 
181747
182852
  // src/setup/upgrade-plan.ts
181748
- import { existsSync as existsSync5 } from "node:fs";
181749
- import { join as join13, relative as relative4 } from "node:path";
182853
+ import { existsSync as existsSync6 } from "node:fs";
182854
+ import { join as join14, relative as relative4 } from "node:path";
181750
182855
  var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
181751
182856
  function reportCliVersion(currentVersion, latest) {
181752
182857
  if (latest.status !== "ok") {
@@ -181764,7 +182869,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
181764
182869
  return explicitTarget ?? discovery.agent.defaultTarget;
181765
182870
  }
181766
182871
  function inspectManagedSkill(input) {
181767
- const destinationDir = join13(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
182872
+ const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
181768
182873
  const path = toRootRelative(input.projectRoot, destinationDir);
181769
182874
  const empty = {
181770
182875
  destination: input.destination,
@@ -181780,7 +182885,7 @@ function inspectManagedSkill(input) {
181780
182885
  if (!input.templateAvailable) {
181781
182886
  return { ...empty, state: "template-missing" };
181782
182887
  }
181783
- const comparison = compareSkillTree(destinationDir, join13(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
182888
+ const comparison = compareSkillTree(destinationDir, join14(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
181784
182889
  const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
181785
182890
  return {
181786
182891
  destination: input.destination,
@@ -181794,13 +182899,13 @@ function inspectManagedSkill(input) {
181794
182899
  function inspectLegacySkills(projectRoot, destination) {
181795
182900
  const reports = [];
181796
182901
  for (const skillName of LEGACY_SKILL_NAMES) {
181797
- const destinationDir = join13(projectRoot, destination, skillName);
182902
+ const destinationDir = join14(projectRoot, destination, skillName);
181798
182903
  const path = toRootRelative(projectRoot, destinationDir);
181799
182904
  const kind = classifyManagedPath(projectRoot, path);
181800
182905
  if (kind === "absent") {
181801
182906
  continue;
181802
182907
  }
181803
- if (kind === "symlinked-parent" && !existsSync5(destinationDir)) {
182908
+ if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
181804
182909
  continue;
181805
182910
  }
181806
182911
  reports.push({ path, kind });
@@ -182064,10 +183169,10 @@ function parseAgentTarget(value) {
182064
183169
  return value;
182065
183170
  }
182066
183171
  function resolveProjectRoot2(root) {
182067
- const projectRoot = resolve3(root);
183172
+ const projectRoot = resolve4(root);
182068
183173
  let stats;
182069
183174
  try {
182070
- stats = statSync4(projectRoot);
183175
+ stats = statSync5(projectRoot);
182071
183176
  } catch {
182072
183177
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
182073
183178
  }
@@ -182092,10 +183197,10 @@ function parseGraphOptions(args) {
182092
183197
  return parseCommandOptions("graph", args);
182093
183198
  }
182094
183199
  function resolveProjectRoot3(root, command) {
182095
- const projectRoot = resolve4(root);
183200
+ const projectRoot = resolve5(root);
182096
183201
  let stats;
182097
183202
  try {
182098
- stats = statSync5(projectRoot);
183203
+ stats = statSync6(projectRoot);
182099
183204
  } catch {
182100
183205
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182101
183206
  }
@@ -182219,7 +183324,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
182219
183324
  if (invalid.length === 0) {
182220
183325
  return;
182221
183326
  }
182222
- return existsSync6(join14(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
183327
+ return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
182223
183328
  }
182224
183329
  function unknownCommandGuidance(command) {
182225
183330
  const suggestion = nearestMatch(command, SUBCOMMANDS);
@@ -182294,7 +183399,7 @@ function run(argv, io = {
182294
183399
  function rootOptionOf(argv, currentDirectory) {
182295
183400
  const flagIndex = argv.indexOf("--root");
182296
183401
  const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
182297
- return value === undefined ? currentDirectory : resolve4(currentDirectory, value);
183402
+ return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
182298
183403
  }
182299
183404
  function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
182300
183405
  const latest = cliRuntime.latest;