docbridge 0.11.0 → 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 (43) hide show
  1. package/CHANGELOG.md +56 -1
  2. package/README.md +16 -9
  3. package/dist/bin/darwin-arm64/SHA256SUMS +3 -3
  4. package/dist/bin/darwin-arm64/docbridge-go-scanner +0 -0
  5. package/dist/bin/darwin-arm64/docbridge-swift-scanner +0 -0
  6. package/dist/bin/darwin-arm64/docbridge_dart_scanner +0 -0
  7. package/dist/bin/linux-x64/SHA256SUMS +2 -2
  8. package/dist/bin/linux-x64/docbridge-go-scanner +0 -0
  9. package/dist/bin/linux-x64/docbridge-swift-scanner +1 -1
  10. package/dist/index.js +1761 -678
  11. package/dist/workers/java/docbridge-java-scanner.jar +0 -0
  12. package/dist/workers/python/docbridge_python_scanner/__init__.py +6 -0
  13. package/dist/workers/python/docbridge_python_scanner/comments.py +177 -0
  14. package/dist/workers/python/docbridge_python_scanner/declarations.py +318 -0
  15. package/dist/workers/python/docbridge_python_scanner/links.py +62 -0
  16. package/dist/workers/python/docbridge_python_scanner/positions.py +75 -0
  17. package/dist/workers/python/docbridge_python_scanner/protocol.py +105 -0
  18. package/dist/workers/python/docbridge_python_scanner/scanner.py +214 -0
  19. package/dist/workers/python/docbridge_python_scanner.py +16 -0
  20. package/dist/workers/ruby/bin/docbridge-ruby-scanner +10 -0
  21. package/dist/workers/ruby/lib/docbridge_ruby_scanner/cli.rb +30 -0
  22. package/dist/workers/ruby/lib/docbridge_ruby_scanner/comments.rb +55 -0
  23. package/dist/workers/ruby/lib/docbridge_ruby_scanner/compat.rb +39 -0
  24. package/dist/workers/ruby/lib/docbridge_ruby_scanner/declarations.rb +230 -0
  25. package/dist/workers/ruby/lib/docbridge_ruby_scanner/links.rb +38 -0
  26. package/dist/workers/ruby/lib/docbridge_ruby_scanner/positions.rb +50 -0
  27. package/dist/workers/ruby/lib/docbridge_ruby_scanner/probe.rb +28 -0
  28. package/dist/workers/ruby/lib/docbridge_ruby_scanner/protocol.rb +161 -0
  29. package/dist/workers/ruby/lib/docbridge_ruby_scanner.rb +21 -0
  30. package/docs/README.md +10 -3
  31. package/docs/user/automation.md +8 -4
  32. package/docs/user/commands.md +11 -3
  33. package/docs/user/configuration.md +104 -17
  34. package/docs/user/getting-started.md +1 -1
  35. package/docs/user/linking.md +79 -10
  36. package/docs/user/troubleshooting.md +22 -9
  37. package/package.json +1 -1
  38. package/schemas/common-output.schema.json +11 -1
  39. package/schemas/context-output.schema.json +1 -1
  40. package/schemas/docbridge.schema.json +95 -2
  41. package/schemas/graph-output.schema.json +1 -1
  42. package/schemas/scanner-worker.schema.json +1 -1
  43. package/templates/skills/docbridge/SKILL.md +12 -2
package/dist/index.js CHANGED
@@ -169132,11 +169132,11 @@ var require_codegen = __commonJS((exports) => {
169132
169132
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
169133
169133
  return `${varKind} ${this.name}${rhs};` + _n;
169134
169134
  }
169135
- optimizeNames(names, constants2) {
169135
+ optimizeNames(names, constants) {
169136
169136
  if (!names[this.name.str])
169137
169137
  return;
169138
169138
  if (this.rhs)
169139
- this.rhs = optimizeExpr(this.rhs, names, constants2);
169139
+ this.rhs = optimizeExpr(this.rhs, names, constants);
169140
169140
  return this;
169141
169141
  }
169142
169142
  get names() {
@@ -169154,10 +169154,10 @@ var require_codegen = __commonJS((exports) => {
169154
169154
  render({ _n }) {
169155
169155
  return `${this.lhs} = ${this.rhs};` + _n;
169156
169156
  }
169157
- optimizeNames(names, constants2) {
169157
+ optimizeNames(names, constants) {
169158
169158
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
169159
169159
  return;
169160
- this.rhs = optimizeExpr(this.rhs, names, constants2);
169160
+ this.rhs = optimizeExpr(this.rhs, names, constants);
169161
169161
  return this;
169162
169162
  }
169163
169163
  get names() {
@@ -169223,8 +169223,8 @@ var require_codegen = __commonJS((exports) => {
169223
169223
  optimizeNodes() {
169224
169224
  return `${this.code}` ? this : undefined;
169225
169225
  }
169226
- optimizeNames(names, constants2) {
169227
- this.code = optimizeExpr(this.code, names, constants2);
169226
+ optimizeNames(names, constants) {
169227
+ this.code = optimizeExpr(this.code, names, constants);
169228
169228
  return this;
169229
169229
  }
169230
169230
  get names() {
@@ -169254,12 +169254,12 @@ var require_codegen = __commonJS((exports) => {
169254
169254
  }
169255
169255
  return nodes.length > 0 ? this : undefined;
169256
169256
  }
169257
- optimizeNames(names, constants2) {
169257
+ optimizeNames(names, constants) {
169258
169258
  const { nodes } = this;
169259
169259
  let i = nodes.length;
169260
169260
  while (i--) {
169261
169261
  const n = nodes[i];
169262
- if (n.optimizeNames(names, constants2))
169262
+ if (n.optimizeNames(names, constants))
169263
169263
  continue;
169264
169264
  subtractNames(names, n.names);
169265
169265
  nodes.splice(i, 1);
@@ -169316,12 +169316,12 @@ var require_codegen = __commonJS((exports) => {
169316
169316
  return;
169317
169317
  return this;
169318
169318
  }
169319
- optimizeNames(names, constants2) {
169319
+ optimizeNames(names, constants) {
169320
169320
  var _a;
169321
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants2);
169322
- if (!(super.optimizeNames(names, constants2) || this.else))
169321
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants);
169322
+ if (!(super.optimizeNames(names, constants) || this.else))
169323
169323
  return;
169324
- this.condition = optimizeExpr(this.condition, names, constants2);
169324
+ this.condition = optimizeExpr(this.condition, names, constants);
169325
169325
  return this;
169326
169326
  }
169327
169327
  get names() {
@@ -169346,10 +169346,10 @@ var require_codegen = __commonJS((exports) => {
169346
169346
  render(opts) {
169347
169347
  return `for(${this.iteration})` + super.render(opts);
169348
169348
  }
169349
- optimizeNames(names, constants2) {
169350
- if (!super.optimizeNames(names, constants2))
169349
+ optimizeNames(names, constants) {
169350
+ if (!super.optimizeNames(names, constants))
169351
169351
  return;
169352
- this.iteration = optimizeExpr(this.iteration, names, constants2);
169352
+ this.iteration = optimizeExpr(this.iteration, names, constants);
169353
169353
  return this;
169354
169354
  }
169355
169355
  get names() {
@@ -169387,10 +169387,10 @@ var require_codegen = __commonJS((exports) => {
169387
169387
  render(opts) {
169388
169388
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
169389
169389
  }
169390
- optimizeNames(names, constants2) {
169391
- if (!super.optimizeNames(names, constants2))
169390
+ optimizeNames(names, constants) {
169391
+ if (!super.optimizeNames(names, constants))
169392
169392
  return;
169393
- this.iterable = optimizeExpr(this.iterable, names, constants2);
169393
+ this.iterable = optimizeExpr(this.iterable, names, constants);
169394
169394
  return this;
169395
169395
  }
169396
169396
  get names() {
@@ -169435,11 +169435,11 @@ var require_codegen = __commonJS((exports) => {
169435
169435
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
169436
169436
  return this;
169437
169437
  }
169438
- optimizeNames(names, constants2) {
169438
+ optimizeNames(names, constants) {
169439
169439
  var _a, _b;
169440
- super.optimizeNames(names, constants2);
169441
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants2);
169442
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
169440
+ super.optimizeNames(names, constants);
169441
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants);
169442
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
169443
169443
  return this;
169444
169444
  }
169445
169445
  get names() {
@@ -169713,7 +169713,7 @@ var require_codegen = __commonJS((exports) => {
169713
169713
  function addExprNames(names, from) {
169714
169714
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
169715
169715
  }
169716
- function optimizeExpr(expr, names, constants2) {
169716
+ function optimizeExpr(expr, names, constants) {
169717
169717
  if (expr instanceof code_1.Name)
169718
169718
  return replaceName(expr);
169719
169719
  if (!canOptimize(expr))
@@ -169728,14 +169728,14 @@ var require_codegen = __commonJS((exports) => {
169728
169728
  return items;
169729
169729
  }, []));
169730
169730
  function replaceName(n) {
169731
- const c = constants2[n.str];
169731
+ const c = constants[n.str];
169732
169732
  if (c === undefined || names[n.str] !== 1)
169733
169733
  return n;
169734
169734
  delete names[n.str];
169735
169735
  return c;
169736
169736
  }
169737
169737
  function canOptimize(e) {
169738
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
169738
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
169739
169739
  }
169740
169740
  }
169741
169741
  function subtractNames(names, from) {
@@ -170197,8 +170197,8 @@ var require_dataType = __commonJS((exports) => {
170197
170197
  return types;
170198
170198
  }
170199
170199
  exports.getSchemaTypes = getSchemaTypes;
170200
- function getJSONTypes(ts2) {
170201
- const types = Array.isArray(ts2) ? ts2 : ts2 ? [ts2] : [];
170200
+ function getJSONTypes(ts3) {
170201
+ const types = Array.isArray(ts3) ? ts3 : ts3 ? [ts3] : [];
170202
170202
  if (types.every(rules_1.isJSONType))
170203
170203
  return types;
170204
170204
  throw new Error("type must be JSONType or JSONType[]: " + types.join(","));
@@ -171223,18 +171223,18 @@ var require_validate = __commonJS((exports) => {
171223
171223
  });
171224
171224
  narrowSchemaTypes(it, types);
171225
171225
  }
171226
- function checkMultipleTypes(it, ts2) {
171227
- if (ts2.length > 1 && !(ts2.length === 2 && ts2.includes("null"))) {
171226
+ function checkMultipleTypes(it, ts3) {
171227
+ if (ts3.length > 1 && !(ts3.length === 2 && ts3.includes("null"))) {
171228
171228
  strictTypesError(it, "use allowUnionTypes to allow union type keyword");
171229
171229
  }
171230
171230
  }
171231
- function checkKeywordTypes(it, ts2) {
171231
+ function checkKeywordTypes(it, ts3) {
171232
171232
  const rules = it.self.RULES.all;
171233
171233
  for (const keyword in rules) {
171234
171234
  const rule = rules[keyword];
171235
171235
  if (typeof rule == "object" && (0, applicability_1.shouldUseRule)(it.schema, rule)) {
171236
171236
  const { type } = rule.definition;
171237
- if (type.length && !type.some((t) => hasApplicableType(ts2, t))) {
171237
+ if (type.length && !type.some((t) => hasApplicableType(ts3, t))) {
171238
171238
  strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`);
171239
171239
  }
171240
171240
  }
@@ -171243,18 +171243,18 @@ var require_validate = __commonJS((exports) => {
171243
171243
  function hasApplicableType(schTs, kwdT) {
171244
171244
  return schTs.includes(kwdT) || kwdT === "number" && schTs.includes("integer");
171245
171245
  }
171246
- function includesType(ts2, t) {
171247
- return ts2.includes(t) || t === "integer" && ts2.includes("number");
171246
+ function includesType(ts3, t) {
171247
+ return ts3.includes(t) || t === "integer" && ts3.includes("number");
171248
171248
  }
171249
171249
  function narrowSchemaTypes(it, withTypes) {
171250
- const ts2 = [];
171250
+ const ts3 = [];
171251
171251
  for (const t of it.dataTypes) {
171252
171252
  if (includesType(withTypes, t))
171253
- ts2.push(t);
171253
+ ts3.push(t);
171254
171254
  else if (withTypes.includes("integer") && t === "number")
171255
- ts2.push("integer");
171255
+ ts3.push("integer");
171256
171256
  }
171257
- it.dataTypes = ts2;
171257
+ it.dataTypes = ts3;
171258
171258
  }
171259
171259
  function strictTypesError(it, msg) {
171260
171260
  const schemaPath = it.schemaEnv.baseId + it.errSchemaPath;
@@ -175576,12 +175576,12 @@ var require_2020 = __commonJS((exports, module) => {
175576
175576
  });
175577
175577
 
175578
175578
  // src/cli/index.ts
175579
- import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
175580
- import { join as join14, resolve as resolve4 } from "node:path";
175579
+ import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync6 } from "node:fs";
175580
+ import { join as join15, resolve as resolve5 } from "node:path";
175581
175581
  // package.json
175582
175582
  var package_default = {
175583
175583
  name: "docbridge",
175584
- version: "0.11.0",
175584
+ version: "0.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 = [];
@@ -175788,11 +175778,46 @@ var KNOWN_CODE_LANGUAGES = [
175788
175778
  "swift",
175789
175779
  "dart",
175790
175780
  "rust",
175791
- "go"
175781
+ "go",
175782
+ "javascript",
175783
+ "python",
175784
+ "ruby"
175792
175785
  ];
175793
175786
  function isCodeLanguage(value) {
175794
175787
  return KNOWN_CODE_LANGUAGES.includes(value);
175795
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
+ }
175796
175821
  function collectCodeFiles(projectRoot, codeInclude) {
175797
175822
  const collected = [];
175798
175823
  const seen = new Set;
@@ -175801,7 +175826,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
175801
175826
  if (entry === undefined) {
175802
175827
  continue;
175803
175828
  }
175804
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175829
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175805
175830
  if (seen.has(relPath)) {
175806
175831
  continue;
175807
175832
  }
@@ -175819,7 +175844,7 @@ function codeFileOwners(projectRoot, codeInclude) {
175819
175844
  if (entry === undefined) {
175820
175845
  continue;
175821
175846
  }
175822
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175847
+ for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
175823
175848
  const existing = owners.get(relPath);
175824
175849
  if (existing === undefined) {
175825
175850
  owners.set(relPath, [language]);
@@ -175830,28 +175855,76 @@ function codeFileOwners(projectRoot, codeInclude) {
175830
175855
  }
175831
175856
  return owners;
175832
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
+ }
175833
175910
 
175834
175911
  // src/config/config.ts
175835
175912
  var CONFIG_FILE_NAME = "docbridge.config.json";
175836
175913
  var EMPTY_CONFIG = {
175837
175914
  include: { code: {}, docs: [] }
175838
175915
  };
175839
- var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
175916
+ var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
175840
175917
  var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
175841
175918
  var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
175842
- var LANGUAGE_SUFFIX = {
175843
- typescript: ".ts",
175844
- swift: ".swift",
175845
- dart: ".dart",
175846
- rust: ".rs",
175847
- go: ".go"
175848
- };
175849
175919
  var LANGUAGE_VISIBILITY = {
175850
175920
  typescript: ["public", "protected", "private"],
175851
175921
  swift: ["public", "open", "internal"],
175852
175922
  dart: ["public"],
175853
175923
  rust: ["pub", "private"],
175854
- go: ["exported", "unexported"]
175924
+ go: ["exported", "unexported"],
175925
+ javascript: ["public", "protected", "private"],
175926
+ python: ["public", "private"],
175927
+ ruby: ["public", "protected", "private"]
175855
175928
  };
175856
175929
  function loadConfig(projectRoot) {
175857
175930
  let rawText;
@@ -175880,7 +175953,7 @@ function resolveConfig(rawText) {
175880
175953
  config: EMPTY_CONFIG,
175881
175954
  ok: false,
175882
175955
  diagnostics: [
175883
- 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.`)
175884
175957
  ]
175885
175958
  };
175886
175959
  }
@@ -175893,57 +175966,61 @@ function resolveConfig(rawText) {
175893
175966
  config: EMPTY_CONFIG,
175894
175967
  ok: false,
175895
175968
  diagnostics: [
175896
- 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}`)
175897
175970
  ]
175898
175971
  };
175899
175972
  }
175900
175973
  const diagnostics = [];
175901
- if (!isPlainObject(parsed)) {
175902
- 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."));
175903
175976
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175904
175977
  }
175905
175978
  for (const key of Object.keys(parsed)) {
175906
175979
  if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
175907
- 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}`));
175908
175981
  }
175909
175982
  }
175910
175983
  if ("$schema" in parsed && typeof parsed.$schema !== "string") {
175911
- 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."));
175912
175985
  }
175986
+ const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
175913
175987
  const include = parsed.include;
175914
- if (!isPlainObject(include)) {
175915
- diagnostics.push(configDiagnostic("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175988
+ if (!isPlainObject2(include)) {
175989
+ diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175916
175990
  return { config: EMPTY_CONFIG, diagnostics, ok: false };
175917
175991
  }
175918
175992
  for (const key of Object.keys(include)) {
175919
175993
  if (!KNOWN_INCLUDE_KEYS.has(key)) {
175920
- 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}`));
175921
175995
  }
175922
175996
  }
175923
175997
  const code = validateCodeInclude(include.code, diagnostics);
175924
- validatePatternArray(include.docs, "docs", ".md", false, diagnostics);
175998
+ validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
175925
175999
  const ok = diagnostics.length === 0;
175926
- 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;
175927
176004
  return { config, diagnostics, ok };
175928
176005
  }
175929
176006
  function validateCodeInclude(value, diagnostics) {
175930
176007
  if (Array.isArray(value)) {
175931
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
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.'));
175932
176009
  return {};
175933
176010
  }
175934
- if (!isPlainObject(value)) {
175935
- 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."));
175936
176013
  return {};
175937
176014
  }
175938
176015
  const keys = Object.keys(value);
175939
176016
  if (keys.length === 0) {
175940
- 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."));
175941
176018
  return {};
175942
176019
  }
175943
176020
  const result = {};
175944
176021
  for (const key of keys) {
175945
176022
  if (!isCodeLanguage(key)) {
175946
- 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(", ")}.`));
175947
176024
  continue;
175948
176025
  }
175949
176026
  const entry = validateCodeEntry(key, value[key], diagnostics);
@@ -175955,20 +176032,20 @@ function validateCodeInclude(value, diagnostics) {
175955
176032
  }
175956
176033
  function validateCodeEntry(language, value, diagnostics) {
175957
176034
  const target = `include.code.${language}`;
175958
- if (!isPlainObject(value)) {
175959
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
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.`));
175960
176037
  return;
175961
176038
  }
175962
176039
  for (const key of Object.keys(value)) {
175963
176040
  if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
175964
- 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}`));
175965
176042
  }
175966
176043
  }
175967
176044
  const before = diagnostics.length;
175968
- 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);
175969
176046
  if ("visibility" in value) {
175970
176047
  if (!isStringArray(value.visibility)) {
175971
- 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.`));
175972
176049
  } else {
175973
176050
  validateVisibilityOptions(language, value.visibility, target, diagnostics);
175974
176051
  }
@@ -175985,47 +176062,49 @@ function validateCodeEntry(language, value, diagnostics) {
175985
176062
  function validateVisibilityOptions(language, values, target, diagnostics) {
175986
176063
  const allowed = LANGUAGE_VISIBILITY[language];
175987
176064
  if (allowed.length === 0) {
175988
- 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}.`));
175989
176066
  return;
175990
176067
  }
175991
176068
  for (const value of values) {
175992
176069
  if (!allowed.includes(value)) {
175993
- 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(", ")}.`));
175994
176071
  }
175995
176072
  }
175996
176073
  }
175997
- function validatePatternArray(value, field, requiredSuffix, excludeDeclarationFiles, diagnostics) {
176074
+ function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
175998
176075
  const target = `include.${field}`;
175999
176076
  if (!Array.isArray(value)) {
176000
- 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.`));
176001
176078
  return;
176002
176079
  }
176003
176080
  if (value.length === 0) {
176004
- 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.`));
176005
176082
  return;
176006
176083
  }
176007
176084
  for (const pattern of value) {
176008
176085
  if (typeof pattern !== "string") {
176009
- 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.`));
176010
176087
  continue;
176011
176088
  }
176012
176089
  const globResult = validateGlobPattern(pattern);
176013
176090
  if (!globResult.ok) {
176014
- 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}`));
176015
176092
  continue;
176016
176093
  }
176017
- const suffixError = checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles);
176094
+ const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
176018
176095
  if (suffixError !== undefined) {
176019
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, suffixError));
176096
+ diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
176020
176097
  }
176021
176098
  }
176022
176099
  }
176023
- function checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles) {
176024
- if (!pattern.endsWith(requiredSuffix)) {
176025
- 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}.`;
176026
176104
  }
176027
- if (excludeDeclarationFiles && pattern.endsWith(".d.ts")) {
176028
- 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.`;
176029
176108
  }
176030
176109
  return;
176031
176110
  }
@@ -176033,15 +176112,15 @@ function detectLanguageOverlap(projectRoot, code) {
176033
176112
  const diagnostics = [];
176034
176113
  for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
176035
176114
  if (owners.length > 1) {
176036
- diagnostics.push(configDiagnostic("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
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.`));
176037
176116
  }
176038
176117
  }
176039
176118
  return diagnostics;
176040
176119
  }
176041
- function configDiagnostic(code, target, message) {
176120
+ function configDiagnostic2(code, target, message) {
176042
176121
  return { severity: "error", code, target, message };
176043
176122
  }
176044
- function isPlainObject(value) {
176123
+ function isPlainObject2(value) {
176045
176124
  return typeof value === "object" && value !== null && !Array.isArray(value);
176046
176125
  }
176047
176126
  function isStringArray(value) {
@@ -176568,6 +176647,11 @@ function positionOf(range) {
176568
176647
  // src/lsp/server.ts
176569
176648
  import { fileURLToPath as fileURLToPath3 } from "node:url";
176570
176649
 
176650
+ // src/shared/error.ts
176651
+ function reasonOf(error) {
176652
+ return error instanceof Error ? error.message : String(error);
176653
+ }
176654
+
176571
176655
  // src/lsp/position.ts
176572
176656
  function toLspPosition(position) {
176573
176657
  return { line: position.line - 1, character: position.column - 1 };
@@ -176951,32 +177035,27 @@ function renderCodeSignatures(state, counterparts) {
176951
177035
  const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
176952
177036
  const fenced = signature.length > 0 ? `
176953
177037
 
176954
- \`\`\`${FENCE_LANGUAGE[symbol.language]}
177038
+ \`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
176955
177039
  ${signature}
176956
177040
  \`\`\`` : "";
176957
177041
  blocks.push(`**${symbol.endpoint}**${fenced}`);
176958
177042
  }
176959
177043
  return blocks.length > 0 ? blocks.join(DIVIDER) : null;
176960
177044
  }
176961
- var FENCE_LANGUAGE = {
176962
- typescript: "ts",
176963
- swift: "swift",
176964
- dart: "dart",
176965
- rust: "rust",
176966
- go: "go"
176967
- };
176968
177045
  var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
177046
+ var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
176969
177047
  function withoutLeadingComments(text, language) {
177048
+ const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
176970
177049
  let index = 0;
176971
177050
  for (;; ) {
176972
177051
  while (index < text.length && /\s/.test(text[index] ?? "")) {
176973
177052
  index += 1;
176974
177053
  }
176975
- if (text.startsWith("//", index)) {
177054
+ if (text.startsWith(lineComment, index)) {
176976
177055
  const newline = text.indexOf(`
176977
177056
  `, index);
176978
177057
  index = newline === -1 ? text.length : newline + 1;
176979
- } else if (text.startsWith("/*", index)) {
177058
+ } else if (lineComment === "//" && text.startsWith("/*", index)) {
176980
177059
  index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
176981
177060
  } else {
176982
177061
  return text.slice(index);
@@ -177517,24 +177596,205 @@ function manifestLink(source, target, entry, side) {
177517
177596
  };
177518
177597
  }
177519
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
+
177520
177700
  // src/scan/code/typescript.ts
177701
+ var import_typescript2 = __toESM(require_typescript(), 1);
177702
+
177703
+ // src/scan/code/typescript-diagnostics.ts
177521
177704
  var import_typescript = __toESM(require_typescript(), 1);
177522
- 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
177523
177776
  var typeScriptAdapter = {
177524
- language: LANGUAGE,
177777
+ language: "typescript",
177525
177778
  scanFile(filePath, content, options) {
177526
177779
  return scanTypeScript(filePath, content, options);
177780
+ },
177781
+ scanFiles(files, options) {
177782
+ return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
177527
177783
  }
177528
177784
  };
177785
+ var javaScriptAdapter = {
177786
+ ...typeScriptAdapter,
177787
+ language: "javascript"
177788
+ };
177529
177789
  var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
177530
177790
  function scanTypeScript(filePath, content, options = {}) {
177531
177791
  const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
177532
- const sourceFile = import_typescript.default.createSourceFile(filePath, content, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
177533
- const parseDiagnostics = getParseDiagnostics(sourceFile);
177534
- if (parseDiagnostics.length > 0) {
177535
- const first = parseDiagnostics[0];
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];
177536
177796
  return {
177537
- language: LANGUAGE,
177797
+ language: scriptLanguage(filePath),
177538
177798
  filePath,
177539
177799
  symbols: [],
177540
177800
  undocumentedSymbols: [],
@@ -177604,11 +177864,13 @@ function scanTypeScript(filePath, content, options = {}) {
177604
177864
  }
177605
177865
  const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
177606
177866
  if (!parsed.ok) {
177607
- diagnostics.push(parsed.diagnostic);
177867
+ diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
177608
177868
  continue;
177609
177869
  }
177610
177870
  if (linkTargetsSeen.has(docTag.rawTarget)) {
177611
- 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));
177612
177874
  continue;
177613
177875
  }
177614
177876
  linkTargetsSeen.add(docTag.rawTarget);
@@ -177624,7 +177886,7 @@ function scanTypeScript(filePath, content, options = {}) {
177624
177886
  }
177625
177887
  }
177626
177888
  return {
177627
- language: LANGUAGE,
177889
+ language: scriptLanguage(filePath),
177628
177890
  filePath,
177629
177891
  symbols,
177630
177892
  undocumentedSymbols,
@@ -177632,15 +177894,60 @@ function scanTypeScript(filePath, content, options = {}) {
177632
177894
  diagnostics
177633
177895
  };
177634
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
+ }
177635
177917
  function getParseDiagnostics(sourceFile) {
177636
177918
  const withDiagnostics = sourceFile;
177637
177919
  return withDiagnostics.parseDiagnostics ?? [];
177638
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
+ }
177639
177946
  function collectDocTags(filePath, sourceFile, statement) {
177640
177947
  const node = jsDocCarrier(statement);
177641
177948
  const location = locationOf(filePath, sourceFile, statement);
177642
177949
  const tags = [];
177643
- for (const tag of import_typescript.default.getJSDocTags(node)) {
177950
+ for (const tag of import_typescript2.default.getJSDocTags(node)) {
177644
177951
  if (tag.tagName.escapedText !== "doc") {
177645
177952
  continue;
177646
177953
  }
@@ -177659,7 +177966,7 @@ function collectDocTags(filePath, sourceFile, statement) {
177659
177966
  return tags;
177660
177967
  }
177661
177968
  function jsDocCarrier(statement) {
177662
- if (import_typescript.default.isVariableStatement(statement)) {
177969
+ if (import_typescript2.default.isVariableStatement(statement)) {
177663
177970
  const declaration = statement.declarationList.declarations[0];
177664
177971
  if (declaration !== undefined) {
177665
177972
  return declaration;
@@ -177705,11 +178012,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
177705
178012
  }
177706
178013
  }
177707
178014
  function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
177708
- if (!import_typescript.default.isConstructorDeclaration(member)) {
178015
+ if (!import_typescript2.default.isConstructorDeclaration(member)) {
177709
178016
  return;
177710
178017
  }
177711
178018
  for (const parameter of member.parameters) {
177712
- if (!import_typescript.default.isParameterPropertyDeclaration(parameter, member)) {
178019
+ if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
177713
178020
  continue;
177714
178021
  }
177715
178022
  if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
@@ -177725,22 +178032,22 @@ function describeContainer(statement) {
177725
178032
  return { name: supportedNameNode(statement)?.text ?? null, members };
177726
178033
  }
177727
178034
  function containerMembers(statement) {
177728
- if (import_typescript.default.isClassDeclaration(statement)) {
178035
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177729
178036
  return statement.members;
177730
178037
  }
177731
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178038
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177732
178039
  return statement.members;
177733
178040
  }
177734
- if (import_typescript.default.isEnumDeclaration(statement)) {
178041
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177735
178042
  return statement.members;
177736
178043
  }
177737
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
177738
- 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;
177739
178046
  }
177740
- if (import_typescript.default.isVariableStatement(statement)) {
178047
+ if (import_typescript2.default.isVariableStatement(statement)) {
177741
178048
  const declarations = statement.declarationList.declarations;
177742
178049
  const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
177743
- return initializer !== undefined && import_typescript.default.isClassExpression(initializer) ? initializer.members : null;
178050
+ return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
177744
178051
  }
177745
178052
  return null;
177746
178053
  }
@@ -177762,24 +178069,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
177762
178069
  };
177763
178070
  }
177764
178071
  function isVisibleMember(member, visibility) {
177765
- if (hasModifier(member, import_typescript.default.SyntaxKind.PrivateKeyword)) {
178072
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
177766
178073
  return visibility.has("private");
177767
178074
  }
177768
- if (hasModifier(member, import_typescript.default.SyntaxKind.ProtectedKeyword)) {
178075
+ if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
177769
178076
  return visibility.has("protected");
177770
178077
  }
177771
178078
  return visibility.has("public");
177772
178079
  }
177773
178080
  function memberIdentity(sourceFile, member) {
177774
- if (import_typescript.default.isConstructorDeclaration(member)) {
177775
- const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript.default.SyntaxKind.ConstructorKeyword);
178081
+ if (import_typescript2.default.isConstructorDeclaration(member)) {
178082
+ const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript2.default.SyntaxKind.ConstructorKeyword);
177776
178083
  return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
177777
178084
  }
177778
- if (!import_typescript.default.isMethodDeclaration(member) && !import_typescript.default.isPropertyDeclaration(member) && !import_typescript.default.isGetAccessorDeclaration(member) && !import_typescript.default.isSetAccessorDeclaration(member) && !import_typescript.default.isMethodSignature(member) && !import_typescript.default.isPropertySignature(member)) {
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)) {
177779
178086
  return null;
177780
178087
  }
177781
178088
  const name = member.name;
177782
- 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;
177783
178090
  }
177784
178091
  function memberSignatureEndOffset(sourceFile, member) {
177785
178092
  const body = member.body;
@@ -177788,28 +178095,28 @@ function memberSignatureEndOffset(sourceFile, member) {
177788
178095
  function supportedNameNode(statement) {
177789
178096
  const isExported = hasExportModifier(statement);
177790
178097
  const isDefault = hasDefaultModifier(statement);
177791
- if (import_typescript.default.isFunctionDeclaration(statement)) {
178098
+ if (import_typescript2.default.isFunctionDeclaration(statement)) {
177792
178099
  if (!isExported || statement.name === undefined) {
177793
178100
  return null;
177794
178101
  }
177795
178102
  return statement.name;
177796
178103
  }
177797
- if (import_typescript.default.isClassDeclaration(statement)) {
178104
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177798
178105
  if (!isExported || statement.name === undefined) {
177799
178106
  return null;
177800
178107
  }
177801
178108
  return statement.name;
177802
178109
  }
177803
- if (import_typescript.default.isInterfaceDeclaration(statement)) {
178110
+ if (import_typescript2.default.isInterfaceDeclaration(statement)) {
177804
178111
  return isExported && !isDefault ? statement.name : null;
177805
178112
  }
177806
- if (import_typescript.default.isTypeAliasDeclaration(statement)) {
178113
+ if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
177807
178114
  return isExported && !isDefault ? statement.name : null;
177808
178115
  }
177809
- if (import_typescript.default.isEnumDeclaration(statement)) {
178116
+ if (import_typescript2.default.isEnumDeclaration(statement)) {
177810
178117
  return isExported && !isDefault ? statement.name : null;
177811
178118
  }
177812
- if (import_typescript.default.isVariableStatement(statement)) {
178119
+ if (import_typescript2.default.isVariableStatement(statement)) {
177813
178120
  if (!isExported) {
177814
178121
  return null;
177815
178122
  }
@@ -177819,23 +178126,23 @@ function supportedNameNode(statement) {
177819
178126
  return null;
177820
178127
  }
177821
178128
  const name = declaration.name;
177822
- return import_typescript.default.isIdentifier(name) ? name : null;
178129
+ return import_typescript2.default.isIdentifier(name) ? name : null;
177823
178130
  }
177824
178131
  return null;
177825
178132
  }
177826
178133
  function hasExportModifier(statement) {
177827
- return hasModifier(statement, import_typescript.default.SyntaxKind.ExportKeyword);
178134
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
177828
178135
  }
177829
178136
  function hasDefaultModifier(statement) {
177830
- return hasModifier(statement, import_typescript.default.SyntaxKind.DefaultKeyword);
178137
+ return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
177831
178138
  }
177832
178139
  function hasModifier(node, kind) {
177833
- 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;
177834
178141
  return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
177835
178142
  }
177836
178143
  function declarationLocation(filePath, sourceFile, node) {
177837
- const carrier = import_typescript.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
177838
- const name = carrier === undefined ? undefined : import_typescript.default.getNameOfDeclaration(carrier);
178144
+ const carrier = import_typescript2.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
178145
+ const name = carrier === undefined ? undefined : import_typescript2.default.getNameOfDeclaration(carrier);
177839
178146
  return locationOf(filePath, sourceFile, name ?? node);
177840
178147
  }
177841
178148
  function locationOf(filePath, sourceFile, node) {
@@ -177854,13 +178161,13 @@ function rangeOfNode(sourceFile, node) {
177854
178161
  return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
177855
178162
  }
177856
178163
  function signatureEndOffset(sourceFile, statement) {
177857
- if (import_typescript.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
178164
+ if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
177858
178165
  return statement.body.getStart(sourceFile);
177859
178166
  }
177860
- if (import_typescript.default.isClassDeclaration(statement)) {
178167
+ if (import_typescript2.default.isClassDeclaration(statement)) {
177861
178168
  return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
177862
178169
  }
177863
- if (import_typescript.default.isVariableStatement(statement)) {
178170
+ if (import_typescript2.default.isVariableStatement(statement)) {
177864
178171
  return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
177865
178172
  }
177866
178173
  return statement.getEnd();
@@ -177876,13 +178183,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
177876
178183
  if (initializer === undefined) {
177877
178184
  return;
177878
178185
  }
177879
- 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)) {
177880
178187
  return initializer.body.getStart(sourceFile);
177881
178188
  }
177882
- if (import_typescript.default.isObjectLiteralExpression(initializer)) {
178189
+ if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
177883
178190
  return initializer.getStart(sourceFile);
177884
178191
  }
177885
- if (import_typescript.default.isClassExpression(initializer)) {
178192
+ if (import_typescript2.default.isClassExpression(initializer)) {
177886
178193
  return classBodyStartOffset(sourceFile, initializer);
177887
178194
  }
177888
178195
  return;
@@ -177897,7 +178204,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
177897
178204
  function makeCodeSymbol(filePath, endpoint, declaration) {
177898
178205
  const symbol = {
177899
178206
  kind: "code",
177900
- language: LANGUAGE,
178207
+ language: scriptLanguage(filePath),
177901
178208
  filePath,
177902
178209
  symbolName: declaration.symbolName,
177903
178210
  canonicalId: declaration.canonicalId,
@@ -177934,224 +178241,20 @@ function firstToken(text) {
177934
178241
  }
177935
178242
  return trimmed.split(/\s+/)[0];
177936
178243
  }
177937
- function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
177938
- const location = { filePath, line: 1, column: 1 };
177939
- if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
177940
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
177941
- location.line = line + 1;
177942
- location.column = character + 1;
177943
- }
177944
- const detail = diagnostic === undefined ? "TypeScript file has a syntactic parse error." : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
177945
- `);
177946
- return {
177947
- severity: "error",
177948
- code: "code_parse_error",
177949
- language: LANGUAGE,
177950
- target: filePath,
177951
- message: `TypeScript parse error: ${detail}`,
177952
- location
177953
- };
177954
- }
177955
- function unsupportedDeclarationDiagnostic(filePath, location) {
177956
- return {
177957
- severity: "warning",
177958
- code: "unsupported_declaration",
177959
- language: LANGUAGE,
177960
- target: filePath,
177961
- message: "@doc is attached to an unsupported declaration. Supported declarations are top-level exported function, class, interface, type, single-declarator const, enum, and named default function or class, plus the identifier-named members of a class, interface, or object type alias.",
177962
- location
177963
- };
177964
- }
177965
- function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
177966
- const diagnostic = {
177967
- severity: "error",
177968
- code: "duplicate_code_symbol",
177969
- language: LANGUAGE,
177970
- target: endpoint,
177971
- message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
177972
- location
177973
- };
177974
- if (range !== undefined) {
177975
- diagnostic.range = range;
177976
- }
177977
- return diagnostic;
177978
- }
177979
- function duplicateLinkDiagnostic(source, target, location, range) {
177980
- const diagnostic = {
177981
- severity: "warning",
177982
- code: "duplicate_link",
177983
- target,
177984
- source,
177985
- message: `Duplicate @doc link from ${source} to ${target}.`,
177986
- location
177987
- };
177988
- if (range !== undefined) {
177989
- diagnostic.range = range;
177990
- }
177991
- return diagnostic;
177992
- }
177993
178244
 
177994
- // src/scan/code/worker/scanner-executable.ts
177995
- import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
177996
- import { join as join5 } from "node:path";
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";
177997
178248
 
177998
- // src/shared/error.ts
177999
- function reasonOf(error) {
178000
- return error instanceof Error ? error.message : String(error);
178001
- }
178249
+ // src/scan/code/worker/runtime-probe.ts
178250
+ import { spawnSync as spawnSync2 } from "node:child_process";
178002
178251
 
178003
- // src/shared/package-root.ts
178004
- 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";
178005
178257
  import { dirname, join as join4 } from "node:path";
178006
- import { fileURLToPath as fileURLToPath2 } from "node:url";
178007
- function resolvePackageRoot(moduleUrl = import.meta.url) {
178008
- const modulePath = fileURLToPath2(moduleUrl);
178009
- let resolved;
178010
- try {
178011
- resolved = realpathSync(modulePath);
178012
- } catch {
178013
- resolved = modulePath;
178014
- }
178015
- let dir = dirname(resolved);
178016
- for (;; ) {
178017
- if (existsSync(join4(dir, "templates", "skills"))) {
178018
- return dir;
178019
- }
178020
- const parent = dirname(dir);
178021
- if (parent === dir) {
178022
- return dir;
178023
- }
178024
- dir = parent;
178025
- }
178026
- }
178027
-
178028
- // src/scan/code/worker/scanner-executable.ts
178029
- var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
178030
- var SCANNER_EXECUTABLE_NAMES = {
178031
- swift: "docbridge-swift-scanner",
178032
- dart: "docbridge_dart_scanner",
178033
- rust: "docbridge-rust-scanner",
178034
- go: "docbridge-go-scanner"
178035
- };
178036
- function scannerPlatformKey() {
178037
- return `${process.platform}-${process.arch}`;
178038
- }
178039
- function scannerExecutableName(language) {
178040
- return SCANNER_EXECUTABLE_NAMES[language];
178041
- }
178042
- function resolveScannerWorkerCommand(language, options = {}) {
178043
- const platformKey = options.platformKey ?? scannerPlatformKey();
178044
- const platformSupported = isSupportedScannerPlatformKey(platformKey);
178045
- const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
178046
- const found = candidates.find((candidate) => existsSync2(candidate));
178047
- if (found !== undefined) {
178048
- const repair = ensureExecutable(found, options.chmod ?? chmodSync);
178049
- if (!repair.ok) {
178050
- return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
178051
- }
178052
- return { ok: true, command: [found] };
178053
- }
178054
- if (!platformSupported) {
178055
- return {
178056
- ok: false,
178057
- diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178058
- };
178059
- }
178060
- return {
178061
- ok: false,
178062
- diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
178063
- };
178064
- }
178065
- function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
178066
- const sourceRoot = options.sourceRoot ?? sourceRootPath();
178067
- const distRoot = options.distRoot ?? distRootPath();
178068
- const executable = scannerExecutableName(language);
178069
- if (language === "swift") {
178070
- return [
178071
- join5(sourceRoot, "packages/swift-scanner/.build/release", executable),
178072
- join5(sourceRoot, "packages/swift-scanner/.build/debug", executable),
178073
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178074
- ];
178075
- }
178076
- if (language === "rust") {
178077
- return [
178078
- join5(sourceRoot, "packages/rust-scanner/target/release", executable),
178079
- join5(sourceRoot, "packages/rust-scanner/target/debug", executable),
178080
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178081
- ];
178082
- }
178083
- if (language === "go") {
178084
- return [
178085
- join5(sourceRoot, "packages/go-scanner/bin", executable),
178086
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178087
- ];
178088
- }
178089
- return [
178090
- join5(sourceRoot, "packages/dart-scanner/bin", executable),
178091
- ...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
178092
- ];
178093
- }
178094
- function ensureExecutable(path, chmod) {
178095
- let mode;
178096
- try {
178097
- mode = statSync(path).mode;
178098
- } catch (error) {
178099
- return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
178100
- }
178101
- if (isExecutableByThisProcess(path)) {
178102
- return { ok: true };
178103
- }
178104
- try {
178105
- chmod(path, mode | 73);
178106
- } catch (error) {
178107
- return {
178108
- ok: false,
178109
- reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
178110
- };
178111
- }
178112
- return { ok: true };
178113
- }
178114
- function isExecutableByThisProcess(path) {
178115
- try {
178116
- accessSync(path, constants.X_OK);
178117
- return true;
178118
- } catch {
178119
- return false;
178120
- }
178121
- }
178122
- function formatMode(mode) {
178123
- return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
178124
- }
178125
- function isSupportedScannerPlatformKey(platformKey) {
178126
- return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
178127
- }
178128
- function scannerRootsFromModuleUrl(moduleUrl) {
178129
- const packageRoot = resolvePackageRoot(moduleUrl);
178130
- return { distRoot: join5(packageRoot, "dist"), sourceRoot: packageRoot };
178131
- }
178132
- function sourceRootPath() {
178133
- return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
178134
- }
178135
- function distRootPath() {
178136
- return scannerRootsFromModuleUrl(import.meta.url).distRoot;
178137
- }
178138
- function scannerUnavailableDiagnostic(language, reason) {
178139
- const label = language.charAt(0).toUpperCase() + language.slice(1);
178140
- return {
178141
- severity: "error",
178142
- code: "code_scanner_unavailable",
178143
- language,
178144
- target: language,
178145
- message: `${label} scanner worker is unavailable: ${reason}`
178146
- };
178147
- }
178148
-
178149
- // src/scan/code/worker/scanner-worker.ts
178150
- var import__2020 = __toESM(require_2020(), 1);
178151
- import { spawnSync } from "node:child_process";
178152
- import { mkdirSync } from "node:fs";
178153
- import { tmpdir } from "node:os";
178154
- import { dirname as dirname2, join as join6 } from "node:path";
178155
178258
  // schemas/common-output.schema.json
178156
178259
  var common_output_schema_default = {
178157
178260
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -178199,7 +178302,17 @@ var common_output_schema_default = {
178199
178302
  target: { type: "string" },
178200
178303
  language: {
178201
178304
  type: "string",
178202
- enum: ["typescript", "swift", "dart", "rust", "go"]
178305
+ enum: [
178306
+ "typescript",
178307
+ "swift",
178308
+ "dart",
178309
+ "rust",
178310
+ "go",
178311
+ "javascript",
178312
+ "python",
178313
+ "ruby",
178314
+ "java"
178315
+ ]
178203
178316
  },
178204
178317
  source: { type: "string" },
178205
178318
  message: { type: "string" },
@@ -178219,7 +178332,7 @@ var scanner_worker_schema_default = {
178219
178332
  $defs: {
178220
178333
  language: {
178221
178334
  type: "string",
178222
- enum: ["typescript", "swift", "dart", "rust", "go"]
178335
+ enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
178223
178336
  },
178224
178337
  codeSymbol: {
178225
178338
  type: "object",
@@ -178335,6 +178448,8 @@ var scanner_worker_schema_default = {
178335
178448
  };
178336
178449
 
178337
178450
  // src/scan/code/worker/scanner-worker.ts
178451
+ var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
178452
+ var KILLED_WORKER_GRACE_MS = 500;
178338
178453
  function createLazyWorkerResponseValidator(compile) {
178339
178454
  let validator;
178340
178455
  return () => {
@@ -178352,15 +178467,29 @@ function compileWorkerResponseValidator() {
178352
178467
  $ref: "#/$defs/response"
178353
178468
  });
178354
178469
  }
178355
- function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178356
- 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 {
178357
178482
  command,
178358
- stdin: JSON.stringify(request)
178359
- });
178483
+ stdin: JSON.stringify(request),
178484
+ stripEnv,
178485
+ timeoutMs: workerTimeoutMs(request.files.length)
178486
+ };
178487
+ }
178488
+ function interpretWorkerProcess(request, command, processResult) {
178360
178489
  if (!processResult.ok) {
178361
178490
  return {
178362
178491
  ok: false,
178363
- 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]),
178364
178493
  stderr: processResult.stderr
178365
178494
  };
178366
178495
  }
@@ -178401,43 +178530,203 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
178401
178530
  }
178402
178531
  function clangModuleCachePath() {
178403
178532
  const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
178404
- 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;
178405
178551
  }
178406
178552
  function runScannerWorkerProcess(input) {
178553
+ const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178407
178554
  try {
178408
- const moduleCachePath = clangModuleCachePath();
178409
- mkdirSync(moduleCachePath, { recursive: true });
178410
178555
  const [executable = "", ...args] = input.command;
178411
178556
  const result = spawnSync(executable, args, {
178412
- env: {
178413
- ...process.env,
178414
- CLANG_MODULE_CACHE_PATH: moduleCachePath
178415
- },
178557
+ env: workerProcessEnv(input.stripEnv),
178416
178558
  input: input.stdin,
178417
178559
  encoding: "utf8",
178418
- maxBuffer: 1024 * 1024 * 1024
178560
+ maxBuffer: maxOutputBytes,
178561
+ killSignal: "SIGKILL",
178562
+ ...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
178419
178563
  });
178420
- const stderr = result.stderr ?? "";
178421
- if (result.error !== undefined) {
178422
- return { ok: false, error: result.error, stderr };
178423
- }
178424
- if (result.status === null) {
178425
- return {
178426
- ok: false,
178427
- error: new Error(`worker terminated by signal ${result.signal ?? "unknown"}`),
178428
- stderr
178429
- };
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;
178430
178590
  }
178431
- return {
178432
- ok: true,
178433
- exitCode: result.status,
178434
- stdout: result.stdout ?? "",
178435
- stderr
178436
- };
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 });
178437
178601
  } catch (error) {
178438
- 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");
178439
178694
  }
178440
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 };
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" });
178729
+ }
178441
178730
  function validateWorkerResponse(value, request) {
178442
178731
  const validator = responseValidator();
178443
178732
  if (!validator(value)) {
@@ -178463,117 +178752,767 @@ function formatSchemaError(errors) {
178463
178752
  const path = error.instancePath || "/";
178464
178753
  return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
178465
178754
  }
178466
- function responseFilesMatchRequest(responseFiles, requestFiles) {
178467
- if (responseFiles.length !== requestFiles.length) {
178468
- return false;
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] };
179221
+ }
179222
+ return;
179223
+ }
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;
179230
+ }
179231
+ return resolve2(projectRoot, executable);
179232
+ }
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;
178469
179238
  }
178470
- return responseFiles.every((file, index) => {
178471
- 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()) {
178472
179254
  return false;
178473
179255
  }
178474
- return file.filePath === requestFiles[index]?.filePath;
178475
- });
179256
+ if (platform !== "win32") {
179257
+ accessSync2(path, constants2.X_OK);
179258
+ }
179259
+ return true;
179260
+ } catch {
179261
+ return false;
179262
+ }
178476
179263
  }
178477
- function isRecord(value) {
178478
- 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;
178479
179274
  }
178480
- function scannerUnavailableDiagnostic2(language, error, executable) {
178481
- const label = languageLabel(language);
178482
- return {
178483
- severity: "error",
178484
- code: "code_scanner_unavailable",
178485
- language,
178486
- target: language,
178487
- message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
178488
- };
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
+ });
178489
179286
  }
178490
- function spawnFailureReason(error, executable) {
178491
- const reason = reasonOf(error);
178492
- if (!isExecDenied(error) || executable === undefined) {
178493
- 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);
178494
179301
  }
178495
- return `${reason}; the scanner is executable but ${dirname2(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
178496
179302
  }
178497
- function isExecDenied(error) {
178498
- if (!(error instanceof Error)) {
178499
- 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}`);
178500
179306
  }
178501
- const code = error.code;
178502
- 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 };
178503
179318
  }
178504
- function scannerFailedDiagnostic(language, reason) {
178505
- 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";
178506
179324
  return {
178507
- severity: "error",
178508
- code: "code_scanner_failed",
178509
- language,
178510
- target: language,
178511
- 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
+ }
178512
179333
  };
178513
179334
  }
178514
- function languageLabel(language) {
178515
- return language.charAt(0).toUpperCase() + language.slice(1);
178516
- }
178517
179335
 
178518
179336
  // src/scan/code/dispatch.ts
178519
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
+ };
178520
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 = {
178521
179365
  language,
178522
179366
  scanFile(filePath, content, options, context) {
178523
- const commandResolution = normalizeScannerCommand(command(context.projectRoot));
178524
- if (!commandResolution.ok) {
178525
- return {
178526
- ...emptyScan(language, filePath),
178527
- diagnostics: [fileScopedScannerDiagnostic(commandResolution.diagnostic, filePath)]
178528
- };
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 [];
178529
179373
  }
178530
- const result = invokeScannerWorker({
178531
- schemaVersion: 1,
178532
- requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
178533
- language,
178534
- projectRoot: context.projectRoot,
178535
- files: [{ filePath, content }],
178536
- options
178537
- }, commandResolution.command, adapterOptions.run);
178538
- if (result.ok) {
178539
- const scan = result.codeFiles[0];
178540
- return scan ?? emptyScan(language, filePath);
179374
+ if (!resolved.ok) {
179375
+ return failedBatch(language, files, resolved.diagnostic);
178541
179376
  }
178542
- return {
178543
- ...emptyScan(language, filePath),
178544
- diagnostics: [fileScopedScannerDiagnostic(result.diagnostic, filePath)]
178545
- };
178546
- }
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 })
178547
179391
  };
179392
+ return { argv, runtime, adapter };
178548
179393
  }
178549
179394
  function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
178550
- const codeFiles = [];
178551
- 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;
178552
179455
  for (const { language, relPath } of files) {
178553
179456
  const result = read(relPath);
178554
179457
  if (!result.ok) {
178555
- diagnostics.push(result.diagnostic);
179458
+ slots.push({ read: false, diagnostic: result.diagnostic });
178556
179459
  continue;
178557
179460
  }
178558
- options.onContent?.(relPath, result.content);
178559
- const adapter = options.adapters?.[language] ?? builtInAdapters[language];
178560
- const entry = codeInclude[language];
178561
- const scanOptions = entry?.visibility !== undefined ? { visibility: entry.visibility } : {};
178562
- const scan = adapter.scanFile(relPath, result.content, scanOptions, {
178563
- projectRoot
178564
- });
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);
178565
179491
  diagnostics.push(...scan.diagnostics);
178566
179492
  codeFiles.push(scan);
178567
179493
  }
178568
179494
  return { codeFiles, diagnostics };
178569
179495
  }
178570
- var builtInAdapters = {
178571
- typescript: typeScriptAdapter,
178572
- swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
178573
- dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
178574
- rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
178575
- go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go"))
178576
- };
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
+ }
178577
179516
  function emptyScan(language, filePath) {
178578
179517
  return {
178579
179518
  language,
@@ -178584,11 +179523,16 @@ function emptyScan(language, filePath) {
178584
179523
  diagnostics: []
178585
179524
  };
178586
179525
  }
178587
- function fileScopedScannerDiagnostic(diagnostic, filePath) {
178588
- return { ...diagnostic, target: filePath };
178589
- }
178590
- function normalizeScannerCommand(value) {
178591
- return Array.isArray(value) ? { ok: true, command: value } : value;
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;
178592
179536
  }
178593
179537
 
178594
179538
  // src/scan/markdown/markdown.ts
@@ -178823,6 +179767,42 @@ function flushDanglingWith(pending, diagnostics, message) {
178823
179767
 
178824
179768
  // src/query/project-scan.ts
178825
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) {
178826
179806
  const configResult = loadConfig(options.projectRoot);
178827
179807
  if (!configResult.ok) {
178828
179808
  return { ok: false, diagnostics: configResult.diagnostics };
@@ -178832,32 +179812,51 @@ function scanProject(options) {
178832
179812
  return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
178833
179813
  }
178834
179814
  const collectCode = options.collectCode ?? collectCodeFiles;
178835
- const collectDocs = options.collectDocs ?? collectFiles;
178836
- const readFile = options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath));
178837
- const diagnostics = [
178838
- ...configResult.diagnostics,
178839
- ...manifestResult.diagnostics
178840
- ];
178841
- const contentByFile = options.keepContent ? new Map : undefined;
178842
- const codeScan = scanCodeFiles(options.projectRoot, collectCode(options.projectRoot, configResult.config.include.code), configResult.config.include.code, readFile, {
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 {
178843
179831
  ...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
178844
- ...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 };
178845
179844
  });
178846
- diagnostics.push(...codeScan.diagnostics);
179845
+ }
179846
+ function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
179847
+ const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
178847
179848
  const docFiles = [];
178848
- for (const relPath of collectDocs(options.projectRoot, configResult.config.include.docs)) {
178849
- const read = readFile(relPath);
179849
+ for (const { relPath, read } of docReads) {
178850
179850
  if (!read.ok) {
178851
179851
  diagnostics.push(read.diagnostic);
178852
179852
  continue;
178853
179853
  }
178854
- contentByFile?.set(relPath, read.content);
178855
179854
  const scan2 = scanMarkdown(relPath, read.content);
178856
179855
  diagnostics.push(...scan2.diagnostics);
178857
179856
  docFiles.push(scan2);
178858
179857
  }
178859
179858
  const applied = applyLinkManifest({
178860
- manifest: manifestResult.manifest,
179859
+ manifest: inputs.manifest,
178861
179860
  codeFiles: codeScan.codeFiles,
178862
179861
  docFiles,
178863
179862
  scanDiagnostics: diagnostics
@@ -178869,25 +179868,26 @@ function scanProject(options) {
178869
179868
  docFiles: applied.docFiles,
178870
179869
  diagnostics
178871
179870
  };
178872
- if (contentByFile !== undefined) {
178873
- scan.contentByFile = contentByFile;
179871
+ if (inputs.contentByFile !== undefined) {
179872
+ scan.contentByFile = inputs.contentByFile;
178874
179873
  }
178875
- if (options.buildGraph === true) {
179874
+ if (buildGraph === true) {
178876
179875
  scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
178877
179876
  }
178878
- return {
178879
- ok: true,
178880
- scan
178881
- };
179877
+ return scan;
178882
179878
  }
178883
179879
 
178884
179880
  // src/lsp/project.ts
178885
179881
  class Project {
178886
179882
  projectRoot;
179883
+ options;
178887
179884
  overlay = new Map;
179885
+ overlayRevision = 0;
178888
179886
  current = emptyState();
178889
- constructor(projectRoot) {
179887
+ cache = emptyCodeScanCache();
179888
+ constructor(projectRoot, options = {}) {
178890
179889
  this.projectRoot = projectRoot;
179890
+ this.options = options;
178891
179891
  }
178892
179892
  get root() {
178893
179893
  return this.projectRoot;
@@ -178897,48 +179897,57 @@ class Project {
178897
179897
  }
178898
179898
  setOverlay(relPath, content) {
178899
179899
  this.overlay.set(relPath, content);
179900
+ this.overlayRevision += 1;
178900
179901
  }
178901
179902
  clearOverlay(relPath) {
178902
179903
  this.overlay.delete(relPath);
179904
+ this.overlayRevision += 1;
178903
179905
  }
178904
179906
  resolve() {
178905
179907
  const outcome = scanProject({
178906
- projectRoot: this.projectRoot,
178907
- collectCode: (_projectRoot, include) => this.collectCode(include),
178908
- collectDocs: (_projectRoot, patterns) => this.collect(patterns, false),
178909
- readFile: (relPath) => this.readContent(relPath),
179908
+ ...this.scanSources(this.overlay),
178910
179909
  buildGraph: true,
178911
179910
  keepContent: true
178912
179911
  });
178913
- if (!outcome.ok) {
178914
- this.current = {
178915
- ...emptyState(),
178916
- diagnostics: sortDiagnostics(outcome.diagnostics)
178917
- };
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);
178918
179931
  return this.current;
178919
- }
178920
- const {
178921
- codeFiles,
178922
- docFiles,
178923
- diagnostics: scanDiagnostics,
178924
- contentByFile,
178925
- graph
178926
- } = outcome.scan;
178927
- const relationship = resolveLinks({
178928
- codeFiles,
178929
- docFiles,
178930
- scanDiagnostics,
178931
- audit: false
178932
179932
  });
178933
- this.current = {
178934
- graph,
178935
- index: buildPositionIndex(graph),
178936
- diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
178937
- contentByFile
179933
+ return {
179934
+ promise,
179935
+ cancel() {
179936
+ cancelled = true;
179937
+ scan.cancel();
179938
+ }
178938
179939
  };
178939
- return this.current;
178940
179940
  }
178941
- collectCode(codeInclude) {
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 }
179948
+ };
179949
+ }
179950
+ collectCode(codeInclude, overlay) {
178942
179951
  const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
178943
179952
  const seen = new Set(onDisk.map((file) => file.relPath));
178944
179953
  const all = [...onDisk];
@@ -178947,11 +179956,11 @@ class Project {
178947
179956
  if (entry === undefined) {
178948
179957
  continue;
178949
179958
  }
178950
- for (const relPath of this.overlay.keys()) {
179959
+ for (const relPath of overlay.keys()) {
178951
179960
  if (seen.has(relPath)) {
178952
179961
  continue;
178953
179962
  }
178954
- if (language === "typescript" && relPath.endsWith(".d.ts")) {
179963
+ if (hasExcludedSuffix(language, relPath)) {
178955
179964
  continue;
178956
179965
  }
178957
179966
  if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
@@ -178962,22 +179971,19 @@ class Project {
178962
179971
  }
178963
179972
  return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
178964
179973
  }
178965
- readContent(relPath) {
178966
- const overlaid = this.overlay.get(relPath);
179974
+ readContent(relPath, overlay) {
179975
+ const overlaid = overlay.get(relPath);
178967
179976
  if (overlaid !== undefined) {
178968
179977
  return { ok: true, content: overlaid };
178969
179978
  }
178970
179979
  return readManagedFile(this.projectRoot, relPath);
178971
179980
  }
178972
- collect(patterns, isCode) {
179981
+ collectDocs(patterns, overlay) {
178973
179982
  const paths = new Set(collectFiles(this.projectRoot, patterns));
178974
- for (const relPath of this.overlay.keys()) {
179983
+ for (const relPath of overlay.keys()) {
178975
179984
  if (paths.has(relPath)) {
178976
179985
  continue;
178977
179986
  }
178978
- if (isCode && relPath.endsWith(".d.ts")) {
178979
- continue;
178980
- }
178981
179987
  if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
178982
179988
  paths.add(relPath);
178983
179989
  }
@@ -178985,6 +179991,19 @@ class Project {
178985
179991
  return [...paths].toSorted(comparePaths);
178986
179992
  }
178987
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
+ }
178988
180007
  function emptyState() {
178989
180008
  const graph = buildLinkGraph([], []);
178990
180009
  return {
@@ -178995,6 +180014,74 @@ function emptyState() {
178995
180014
  };
178996
180015
  }
178997
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
+
178998
180085
  // src/lsp/transport.ts
178999
180086
  var HEADER_SEPARATOR = `\r
179000
180087
  \r
@@ -179062,6 +180149,10 @@ function parseContentLength(headerText) {
179062
180149
  }
179063
180150
 
179064
180151
  // src/lsp/server.ts
180152
+ var GLOBAL_TIMERS = {
180153
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
180154
+ clearTimeout: (handle) => clearTimeout(handle)
180155
+ };
179065
180156
  var CAPABILITIES = {
179066
180157
  textDocumentSync: 1,
179067
180158
  hoverProvider: true,
@@ -179072,44 +180163,55 @@ var CAPABILITIES = {
179072
180163
  class Server {
179073
180164
  send;
179074
180165
  project = null;
180166
+ scheduler = null;
179075
180167
  openFiles = new Set;
179076
- dirty = true;
179077
- timer = null;
179078
180168
  shuttingDown = false;
179079
180169
  makeProject;
179080
180170
  debounceMs;
179081
180171
  onExit;
180172
+ timers;
180173
+ onScanError;
179082
180174
  constructor(send, options = {}) {
179083
180175
  this.send = send;
179084
180176
  this.makeProject = options.makeProject ?? ((root) => new Project(root));
179085
180177
  this.debounceMs = options.debounceMs ?? 50;
179086
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
+ `));
179087
180182
  }
179088
180183
  handle(message) {
179089
180184
  switch (message.method) {
179090
180185
  case "initialize":
179091
180186
  this.respond(message.id, { capabilities: CAPABILITIES });
179092
- this.project = this.makeProject(resolveRoot(message.params));
180187
+ this.start(this.makeProject(resolveRoot(message.params)));
179093
180188
  break;
179094
180189
  case "initialized":
179095
- this.flush();
180190
+ this.scheduler?.request("now");
179096
180191
  break;
179097
180192
  case "shutdown":
179098
180193
  this.shuttingDown = true;
179099
- this.clearTimer();
180194
+ this.scheduler?.stop();
179100
180195
  this.respond(message.id, null);
179101
180196
  break;
179102
180197
  case "exit":
180198
+ this.scheduler?.stop();
179103
180199
  this.onExit(this.shuttingDown ? 0 : 1);
179104
180200
  break;
179105
180201
  case "textDocument/didOpen":
179106
- this.onDidOpen(message.params);
180202
+ if (!this.shuttingDown) {
180203
+ this.onDidOpen(message.params);
180204
+ }
179107
180205
  break;
179108
180206
  case "textDocument/didChange":
179109
- this.onDidChange(message.params);
180207
+ if (!this.shuttingDown) {
180208
+ this.onDidChange(message.params);
180209
+ }
179110
180210
  break;
179111
180211
  case "textDocument/didClose":
179112
- this.onDidClose(message.params);
180212
+ if (!this.shuttingDown) {
180213
+ this.onDidClose(message.params);
180214
+ }
179113
180215
  break;
179114
180216
  case "textDocument/hover":
179115
180217
  this.respond(message.id, this.onHover(message.params));
@@ -179126,6 +180228,16 @@ class Server {
179126
180228
  }
179127
180229
  }
179128
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
+ }
179129
180241
  onDidOpen(params) {
179130
180242
  const doc = textDocument(params);
179131
180243
  const rel = this.relPath(doc?.uri);
@@ -179135,8 +180247,7 @@ class Server {
179135
180247
  }
179136
180248
  this.openFiles.add(rel);
179137
180249
  this.project.setOverlay(rel, text);
179138
- this.dirty = true;
179139
- this.flush();
180250
+ this.scheduler?.request("now");
179140
180251
  }
179141
180252
  onDidChange(params) {
179142
180253
  const record = params;
@@ -179147,7 +180258,7 @@ class Server {
179147
180258
  return;
179148
180259
  }
179149
180260
  this.project.setOverlay(rel, text);
179150
- this.scheduleFlush();
180261
+ this.scheduler?.request("debounced");
179151
180262
  }
179152
180263
  onDidClose(params) {
179153
180264
  const doc = textDocument(params);
@@ -179157,9 +180268,8 @@ class Server {
179157
180268
  }
179158
180269
  this.openFiles.delete(rel);
179159
180270
  this.project.clearOverlay(rel);
179160
- this.dirty = true;
179161
180271
  this.publish(rel, []);
179162
- this.flush();
180272
+ this.scheduler?.request("now");
179163
180273
  }
179164
180274
  onHover(params) {
179165
180275
  const located = this.locate(params);
@@ -179198,7 +180308,6 @@ class Server {
179198
180308
  if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
179199
180309
  return null;
179200
180310
  }
179201
- this.flush();
179202
180311
  return {
179203
180312
  state: this.project.state,
179204
180313
  rel,
@@ -179206,28 +180315,14 @@ class Server {
179206
180315
  position: fromLspPosition({ line: position.line, character: position.character })
179207
180316
  };
179208
180317
  }
179209
- scheduleFlush() {
179210
- this.dirty = true;
179211
- this.clearTimer();
179212
- this.timer = setTimeout(() => {
179213
- this.timer = null;
179214
- this.flush();
179215
- }, this.debounceMs);
179216
- }
179217
- flush() {
179218
- this.clearTimer();
179219
- if (this.project === null || !this.dirty) {
179220
- return;
179221
- }
179222
- this.dirty = false;
179223
- const state = this.project.resolve();
180318
+ publishOpenFiles(state) {
179224
180319
  for (const rel of this.openFiles) {
179225
180320
  const content = state.contentByFile.get(rel) ?? "";
179226
180321
  this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
179227
180322
  }
179228
180323
  }
179229
180324
  publish(rel, diagnostics) {
179230
- if (this.project === null) {
180325
+ if (this.project === null || this.shuttingDown) {
179231
180326
  return;
179232
180327
  }
179233
180328
  this.send({
@@ -179248,12 +180343,6 @@ class Server {
179248
180343
  }
179249
180344
  this.send({ jsonrpc: "2.0", id, result });
179250
180345
  }
179251
- clearTimer() {
179252
- if (this.timer !== null) {
179253
- clearTimeout(this.timer);
179254
- this.timer = null;
179255
- }
179256
- }
179257
180346
  }
179258
180347
  function resolveRoot(params) {
179259
180348
  const record = params;
@@ -179347,7 +180436,7 @@ function check(options) {
179347
180436
  }
179348
180437
 
179349
180438
  // src/query/related.ts
179350
- import { isAbsolute as isAbsolute2, relative as relative2 } from "node:path";
180439
+ import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
179351
180440
  function normalizeChangedPaths(projectRoot, paths) {
179352
180441
  const normalized = new Set;
179353
180442
  for (const raw of paths) {
@@ -179355,7 +180444,7 @@ function normalizeChangedPaths(projectRoot, paths) {
179355
180444
  if (trimmed === "") {
179356
180445
  continue;
179357
180446
  }
179358
- const relativized = isAbsolute2(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
180447
+ const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
179359
180448
  normalized.add(relativized.replace(/^(\.\/)+/, ""));
179360
180449
  }
179361
180450
  return [...normalized];
@@ -179777,7 +180866,7 @@ function comparePairs(left, right) {
179777
180866
  // src/setup/update-cache.ts
179778
180867
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
179779
180868
  import { homedir } from "node:os";
179780
- import { dirname as dirname3, join as join7 } from "node:path";
180869
+ import { dirname as dirname3, join as join8 } from "node:path";
179781
180870
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
179782
180871
  var UPDATE_CACHE_SCHEMA_VERSION = 1;
179783
180872
  function resolveUpdateCachePath(env = process.env, home = homedir()) {
@@ -179785,8 +180874,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
179785
180874
  if (explicit !== undefined && explicit.length > 0) {
179786
180875
  return explicit;
179787
180876
  }
179788
- const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join7(home, ".cache");
179789
- return join7(cacheRoot, "docbridge", "update-check.json");
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");
179790
180879
  }
179791
180880
  function readUpdateCache(path) {
179792
180881
  let rawText;
@@ -180030,7 +181119,7 @@ function isEnabledFlag(value) {
180030
181119
 
180031
181120
  // src/cli/docs.ts
180032
181121
  import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
180033
- import { join as join8 } from "node:path";
181122
+ import { join as join9 } from "node:path";
180034
181123
 
180035
181124
  // src/cli/errors.ts
180036
181125
  class CliError extends Error {
@@ -180201,7 +181290,7 @@ function parseDocument(name, source) {
180201
181290
  };
180202
181291
  }
180203
181292
  function createFileDocumentationReader(packageRoot) {
180204
- const docsRoot = join8(packageRoot, "docs", "user");
181293
+ const docsRoot = join9(packageRoot, "docs", "user");
180205
181294
  function documents() {
180206
181295
  let fileNames;
180207
181296
  try {
@@ -180214,7 +181303,7 @@ function createFileDocumentationReader(packageRoot) {
180214
181303
  }
180215
181304
  return fileNames.map((fileName) => {
180216
181305
  const name = fileName.slice(0, -".md".length);
180217
- return parseDocument(name, readFileSync5(join8(docsRoot, fileName), "utf8"));
181306
+ return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
180218
181307
  });
180219
181308
  }
180220
181309
  return {
@@ -180611,12 +181700,12 @@ function hasHelpFlag(args) {
180611
181700
  }
180612
181701
 
180613
181702
  // src/cli/init.ts
180614
- import { readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
180615
- import { join as join12, resolve as resolve2 } from "node:path";
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";
180616
181705
 
180617
181706
  // src/setup/init-discovery.ts
180618
- import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
180619
- import { basename, dirname as dirname4, join as join9 } from "node:path";
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";
180620
181709
  var HIGH_CONFIDENCE_DIR_NAMES = new Set([
180621
181710
  "docs/specs",
180622
181711
  "specs",
@@ -180646,22 +181735,39 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
180646
181735
  "contributing"
180647
181736
  ]);
180648
181737
  var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
180649
- var TYPESCRIPT_PATTERNS = [
180650
- "src/**/*.ts",
180651
- "lib/**/*.ts",
180652
- "packages/*/src/**/*.ts",
180653
- "apps/*/src/**/*.ts"
180654
- ];
181738
+ var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
181739
+ var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
181740
+ var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
180655
181741
  var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
180656
181742
  var DART_PATTERNS = ["lib/**/*.dart"];
180657
181743
  var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
180658
181744
  var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
181745
+ var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
180659
181746
  var LANGUAGE_PATTERNS = {
180660
181747
  typescript: TYPESCRIPT_PATTERNS,
180661
181748
  swift: SWIFT_PATTERNS,
180662
181749
  dart: DART_PATTERNS,
180663
181750
  rust: RUST_PATTERNS,
180664
- go: GO_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
180665
181771
  };
180666
181772
  function discoverRepository(projectRoot) {
180667
181773
  return {
@@ -180745,8 +181851,8 @@ function discoverCodeScope(projectRoot) {
180745
181851
  };
180746
181852
  }
180747
181853
  function discoverAgentTarget(projectRoot) {
180748
- const hasAgentsDir = existsSync3(join9(projectRoot, ".agents"));
180749
- const hasClaudeDir = existsSync3(join9(projectRoot, ".claude"));
181854
+ const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
181855
+ const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
180750
181856
  let defaultTarget = "none";
180751
181857
  let recommendedTarget = "none";
180752
181858
  let message;
@@ -180773,7 +181879,7 @@ function discoverAgentTarget(projectRoot) {
180773
181879
  };
180774
181880
  }
180775
181881
  function collectMarkdownFiles(projectRoot, currentDir = ".") {
180776
- const absoluteDir = join9(projectRoot, currentDir);
181882
+ const absoluteDir = join10(projectRoot, currentDir);
180777
181883
  let entries;
180778
181884
  try {
180779
181885
  entries = readdirSync3(absoluteDir);
@@ -180787,10 +181893,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
180787
181893
  if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
180788
181894
  continue;
180789
181895
  }
180790
- const absolutePath = join9(projectRoot, relPath);
181896
+ const absolutePath = join10(projectRoot, relPath);
180791
181897
  let stats;
180792
181898
  try {
180793
- stats = statSync2(absolutePath);
181899
+ stats = statSync3(absolutePath);
180794
181900
  } catch {
180795
181901
  continue;
180796
181902
  }
@@ -180828,8 +181934,22 @@ function scoreDocsDirectory(directory) {
180828
181934
  }
180829
181935
  return score;
180830
181936
  }
181937
+ function scriptPatterns(language) {
181938
+ return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
181939
+ }
180831
181940
  function activeCodePatterns(projectRoot, language) {
180832
- 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`)];
180833
181953
  }
180834
181954
  function countCodeFiles(projectRoot, patterns, language) {
180835
181955
  const seen = new Set;
@@ -180845,33 +181965,8 @@ function countCodeFiles(projectRoot, patterns, language) {
180845
181965
  function isExcludedCodeFile(filePath, language) {
180846
181966
  const lower = filePath.toLowerCase();
180847
181967
  const segments = filePath.split("/");
180848
- if (language === "typescript") {
180849
- if (lower.endsWith(".d.ts")) {
180850
- return true;
180851
- }
180852
- if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
180853
- return true;
180854
- }
180855
- }
180856
- if (language === "swift") {
180857
- if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
180858
- return true;
180859
- }
180860
- }
180861
- if (language === "dart") {
180862
- if (lower.endsWith("_test.dart") || segments.includes("test")) {
180863
- return true;
180864
- }
180865
- }
180866
- if (language === "rust") {
180867
- if (segments.includes("target") || segments.includes("tests") || segments.includes("benches") || segments.includes("examples")) {
180868
- return true;
180869
- }
180870
- }
180871
- if (language === "go") {
180872
- if (lower.endsWith("_test.go") || segments.includes("vendor") || segments.includes("testdata")) {
180873
- return true;
180874
- }
181968
+ if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((segment) => segments.includes(segment))) {
181969
+ return true;
180875
181970
  }
180876
181971
  if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
180877
181972
  return true;
@@ -180880,17 +181975,17 @@ function isExcludedCodeFile(filePath, language) {
180880
181975
  }
180881
181976
 
180882
181977
  // src/setup/init-plan.ts
180883
- import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
180884
- import { join as join11, relative as relative3 } from "node:path";
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";
180885
181980
 
180886
181981
  // src/setup/skill-assets.ts
180887
181982
  import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
180888
- import { join as join10 } from "node:path";
181983
+ import { join as join11 } from "node:path";
180889
181984
  function classifyManagedPath(projectRoot, relativePath) {
180890
181985
  const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
180891
181986
  let current = projectRoot;
180892
181987
  for (const [index, segment] of segments.entries()) {
180893
- current = join10(current, segment);
181988
+ current = join11(current, segment);
180894
181989
  const isLast = index === segments.length - 1;
180895
181990
  let stats;
180896
181991
  try {
@@ -180937,7 +182032,7 @@ function compareSkillTree(installedDir, templateDir) {
180937
182032
  continue;
180938
182033
  }
180939
182034
  installedFiles.delete(file);
180940
- if (readFileOrUndefined(join10(installedDir, file)) !== readFileOrUndefined(join10(templateDir, file))) {
182035
+ if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
180941
182036
  modified.push(file);
180942
182037
  }
180943
182038
  }
@@ -180948,7 +182043,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180948
182043
  return;
180949
182044
  }
180950
182045
  const kind = classifyManagedPath(projectRoot, operation.path);
180951
- const destinationDir = join10(projectRoot, operation.path);
182046
+ const destinationDir = join11(projectRoot, operation.path);
180952
182047
  if (operation.action === "remove") {
180953
182048
  if (kind === "directory") {
180954
182049
  rmSync(destinationDir, { recursive: true, force: true });
@@ -180966,7 +182061,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180966
182061
  rmSync(destinationDir, { recursive: true, force: true });
180967
182062
  }
180968
182063
  mkdirSync3(destinationDir, { recursive: true });
180969
- cpSync(join10(packageRoot, "templates", "skills", skillName), destinationDir, {
182064
+ cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
180970
182065
  recursive: true,
180971
182066
  force: true
180972
182067
  });
@@ -180974,7 +182069,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
180974
182069
  function collectSkillFiles(root, prefix, files) {
180975
182070
  let entries;
180976
182071
  try {
180977
- entries = readdirSync4(join10(root, prefix), { withFileTypes: true });
182072
+ entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
180978
182073
  } catch {
180979
182074
  return;
180980
182075
  }
@@ -181085,14 +182180,14 @@ function buildConfigFromScope(scope) {
181085
182180
  };
181086
182181
  }
181087
182182
  function listDistributableSkills(packageRoot) {
181088
- const skillsRoot = join11(packageRoot, "templates", "skills");
182183
+ const skillsRoot = join12(packageRoot, "templates", "skills");
181089
182184
  let entries;
181090
182185
  try {
181091
182186
  entries = readdirSync5(skillsRoot);
181092
182187
  } catch {
181093
182188
  return [];
181094
182189
  }
181095
- 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();
181096
182191
  }
181097
182192
  function formatInitPlan(plan) {
181098
182193
  const lines = [];
@@ -181197,7 +182292,7 @@ function planSkillOperations(input) {
181197
182292
  if (!availableSkills.has(skillName)) {
181198
182293
  continue;
181199
182294
  }
181200
- const destinationDir = join11(input.projectRoot, destination, skillName);
182295
+ const destinationDir = join12(input.projectRoot, destination, skillName);
181201
182296
  const path = relative3(input.projectRoot, destinationDir);
181202
182297
  const kind = classifyManagedPath(input.projectRoot, path);
181203
182298
  if (kind !== "absent" && kind !== "directory") {
@@ -181236,7 +182331,7 @@ function planLegacySkillOperations(input) {
181236
182331
  const operations = [];
181237
182332
  const leftover = [];
181238
182333
  for (const skillName of LEGACY_SKILL_NAMES) {
181239
- const destinationDir = join11(input.projectRoot, input.destination, skillName);
182334
+ const destinationDir = join12(input.projectRoot, input.destination, skillName);
181240
182335
  const path = relative3(input.projectRoot, destinationDir);
181241
182336
  const kind = classifyManagedPath(input.projectRoot, path);
181242
182337
  if (kind === "absent") {
@@ -181316,7 +182411,7 @@ function resolveInitWithAgentTarget(discovery, options) {
181316
182411
  return { target: discovery.agent.defaultTarget, error: undefined };
181317
182412
  }
181318
182413
  function readExistingConfig(projectRoot) {
181319
- const configPath = join11(projectRoot, CONFIG_FILE_NAME2);
182414
+ const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
181320
182415
  let rawText;
181321
182416
  try {
181322
182417
  rawText = readFileSync7(configPath, "utf8");
@@ -181567,7 +182662,7 @@ function executePlan(projectRoot, plan, packageRoot) {
181567
182662
  }
181568
182663
  }
181569
182664
  function executeFileOperation(projectRoot, operation) {
181570
- const absolutePath = join12(projectRoot, operation.path);
182665
+ const absolutePath = join13(projectRoot, operation.path);
181571
182666
  if (operation.action === "create" && operation.content !== undefined) {
181572
182667
  writeFileSync2(absolutePath, operation.content, "utf8");
181573
182668
  }
@@ -181576,10 +182671,10 @@ function executeFileOperation(projectRoot, operation) {
181576
182671
  }
181577
182672
  }
181578
182673
  function resolveProjectRoot(root, command) {
181579
- const projectRoot = resolve2(root);
182674
+ const projectRoot = resolve3(root);
181580
182675
  let stats;
181581
182676
  try {
181582
- stats = statSync3(projectRoot);
182677
+ stats = statSync4(projectRoot);
181583
182678
  } catch {
181584
182679
  throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
181585
182680
  }
@@ -181614,24 +182709,12 @@ ${block.content}`;
181614
182709
  const fence = codeFence(block.content);
181615
182710
  return `${header}
181616
182711
 
181617
- ${fence}${fenceLanguage(block.language)}
182712
+ ${fence}${fenceLanguage(block)}
181618
182713
  ${block.content}
181619
182714
  ${fence}`;
181620
182715
  }
181621
- function fenceLanguage(language) {
181622
- if (language === "swift") {
181623
- return "swift";
181624
- }
181625
- if (language === "dart") {
181626
- return "dart";
181627
- }
181628
- if (language === "rust") {
181629
- return "rust";
181630
- }
181631
- if (language === "go") {
181632
- return "go";
181633
- }
181634
- return "ts";
182716
+ function fenceLanguage(block) {
182717
+ return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
181635
182718
  }
181636
182719
  function codeFence(content) {
181637
182720
  let longestRun = 0;
@@ -181763,12 +182846,12 @@ function formatRelatedSummary(summary) {
181763
182846
  }
181764
182847
 
181765
182848
  // src/cli/upgrade.ts
181766
- import { statSync as statSync4 } from "node:fs";
181767
- import { resolve as resolve3 } from "node:path";
182849
+ import { statSync as statSync5 } from "node:fs";
182850
+ import { resolve as resolve4 } from "node:path";
181768
182851
 
181769
182852
  // src/setup/upgrade-plan.ts
181770
- import { existsSync as existsSync5 } from "node:fs";
181771
- 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";
181772
182855
  var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
181773
182856
  function reportCliVersion(currentVersion, latest) {
181774
182857
  if (latest.status !== "ok") {
@@ -181786,7 +182869,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
181786
182869
  return explicitTarget ?? discovery.agent.defaultTarget;
181787
182870
  }
181788
182871
  function inspectManagedSkill(input) {
181789
- const destinationDir = join13(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
182872
+ const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
181790
182873
  const path = toRootRelative(input.projectRoot, destinationDir);
181791
182874
  const empty = {
181792
182875
  destination: input.destination,
@@ -181802,7 +182885,7 @@ function inspectManagedSkill(input) {
181802
182885
  if (!input.templateAvailable) {
181803
182886
  return { ...empty, state: "template-missing" };
181804
182887
  }
181805
- 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));
181806
182889
  const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
181807
182890
  return {
181808
182891
  destination: input.destination,
@@ -181816,13 +182899,13 @@ function inspectManagedSkill(input) {
181816
182899
  function inspectLegacySkills(projectRoot, destination) {
181817
182900
  const reports = [];
181818
182901
  for (const skillName of LEGACY_SKILL_NAMES) {
181819
- const destinationDir = join13(projectRoot, destination, skillName);
182902
+ const destinationDir = join14(projectRoot, destination, skillName);
181820
182903
  const path = toRootRelative(projectRoot, destinationDir);
181821
182904
  const kind = classifyManagedPath(projectRoot, path);
181822
182905
  if (kind === "absent") {
181823
182906
  continue;
181824
182907
  }
181825
- if (kind === "symlinked-parent" && !existsSync5(destinationDir)) {
182908
+ if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
181826
182909
  continue;
181827
182910
  }
181828
182911
  reports.push({ path, kind });
@@ -182086,10 +183169,10 @@ function parseAgentTarget(value) {
182086
183169
  return value;
182087
183170
  }
182088
183171
  function resolveProjectRoot2(root) {
182089
- const projectRoot = resolve3(root);
183172
+ const projectRoot = resolve4(root);
182090
183173
  let stats;
182091
183174
  try {
182092
- stats = statSync4(projectRoot);
183175
+ stats = statSync5(projectRoot);
182093
183176
  } catch {
182094
183177
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
182095
183178
  }
@@ -182114,10 +183197,10 @@ function parseGraphOptions(args) {
182114
183197
  return parseCommandOptions("graph", args);
182115
183198
  }
182116
183199
  function resolveProjectRoot3(root, command) {
182117
- const projectRoot = resolve4(root);
183200
+ const projectRoot = resolve5(root);
182118
183201
  let stats;
182119
183202
  try {
182120
- stats = statSync5(projectRoot);
183203
+ stats = statSync6(projectRoot);
182121
183204
  } catch {
182122
183205
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182123
183206
  }
@@ -182241,7 +183324,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
182241
183324
  if (invalid.length === 0) {
182242
183325
  return;
182243
183326
  }
182244
- return existsSync6(join14(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
183327
+ return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
182245
183328
  }
182246
183329
  function unknownCommandGuidance(command) {
182247
183330
  const suggestion = nearestMatch(command, SUBCOMMANDS);
@@ -182316,7 +183399,7 @@ function run(argv, io = {
182316
183399
  function rootOptionOf(argv, currentDirectory) {
182317
183400
  const flagIndex = argv.indexOf("--root");
182318
183401
  const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
182319
- return value === undefined ? currentDirectory : resolve4(currentDirectory, value);
183402
+ return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
182320
183403
  }
182321
183404
  function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
182322
183405
  const latest = cliRuntime.latest;