docbridge 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.1] - 2026-10-06
11
+
12
+ ### Fixed
13
+
14
+ - A worker that a scan started no longer outlives the CLI. On POSIX systems
15
+ each worker runs in its own process group, a time or output limit kills the
16
+ whole group, and `check`, `related`, `context`, and `graph` kill their
17
+ running workers when the CLI receives `SIGINT`, `SIGTERM`, or `SIGHUP`
18
+ during a scan, then end by that signal. Before, a worker that ran past the
19
+ CLI, or a process the worker started, could keep running.
20
+
10
21
  ## [0.13.0] - 2026-10-01
11
22
 
12
23
  ### Added
@@ -547,7 +558,8 @@ Initial release of the SpecLink CLI.
547
558
  - `speclink check` command with `--root`, `--json`, and `--audit` options.
548
559
  - `speclink --version` (alias `-v`) and `speclink --help` (alias `-h`).
549
560
 
550
- [Unreleased]: https://github.com/salan70/docbridge/compare/v0.13.0...HEAD
561
+ [Unreleased]: https://github.com/salan70/docbridge/compare/v0.13.1...HEAD
562
+ [0.13.1]: https://github.com/salan70/docbridge/releases/tag/v0.13.1
551
563
  [0.13.0]: https://github.com/salan70/docbridge/releases/tag/v0.13.0
552
564
  [0.12.0]: https://github.com/salan70/docbridge/releases/tag/v0.12.0
553
565
  [0.11.0]: https://github.com/salan70/docbridge/releases/tag/v0.11.0
@@ -1,4 +1,4 @@
1
- bb2e127396b445a443c6d9958430b9af7e992ecca54cac49ce8feb88c199f460 docbridge-swift-scanner
2
- 32032f9e2c703f4ab4976361f15f723a58c675545edce90fdc673d3094fe154e docbridge_dart_scanner
1
+ 41027ff70b494c96a27c0a4e373a070b47222323d0fb8941f4e694eeaa2f2ad9 docbridge-swift-scanner
2
+ 42e6858926db804a07efc8b370b0744e9b23c691b8a363461e5286115c7950ef docbridge_dart_scanner
3
3
  7fc3e6842b3e1bf425b8fd63e241eee7e64875f16be7ba8d1ae4a25baa7f2338 docbridge-rust-scanner
4
- 1a7160cf70cb6ff1517d72685b6ecbfe1878ed1fed013fef706e5f2405ddbce0 docbridge-go-scanner
4
+ 720b5a88fda4a2ffd10660bae19dd5070989fbe5c9463421a180f17bb50b7915 docbridge-go-scanner
@@ -1,4 +1,4 @@
1
- 6e78c1c79c16572ed0759bfcf24ddd4cc77c7d1d699d19dc0279a9abca38d248 docbridge-swift-scanner
1
+ 3f2005f4df20fcf6001111724eac5a9ece412edb5457e3450bd67420e85ba8e3 docbridge-swift-scanner
2
2
  b75b14006ca792ed3ca8faf7ceee9e4d33c895f27dd416b640818cf8a4bdd9c8 docbridge_dart_scanner
3
3
  e0da7b8cc6ee23704f5fe8514cc309f0bb60380c77b55e3bc5ed113abd1f3478 docbridge-rust-scanner
4
- af845c263f65cc5db5beb01b4f4f7251ce449391986b4e288c03abfa04f7e708 docbridge-go-scanner
4
+ 2b8820630246507cdaca9de288dc8a0eb5d870db36287cf8ffd6ec31ce13b328 docbridge-go-scanner
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/bin/linux-x64/docbridge-swift-scanner
3
3
  size: 40932352 bytes
4
- sha256: 6e78c1c79c16572ed0759bfcf24ddd4cc77c7d1d699d19dc0279a9abca38d248
4
+ sha256: 3f2005f4df20fcf6001111724eac5a9ece412edb5457e3450bd67420e85ba8e3
package/dist/index.js CHANGED
@@ -175581,7 +175581,7 @@ 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.13.0",
175584
+ version: "0.13.1",
175585
175585
  description: "A CLI that links Markdown specifications and code declarations. Runs on Node.js and Bun.",
175586
175586
  keywords: [
175587
175587
  "bun",
@@ -176065,10 +176065,6 @@ function validateCodeEntry(language, value, diagnostics) {
176065
176065
  }
176066
176066
  function validateVisibilityOptions(language, values, target, diagnostics) {
176067
176067
  const allowed = LANGUAGE_VISIBILITY[language];
176068
- if (allowed.length === 0) {
176069
- diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
176070
- return;
176071
- }
176072
176068
  for (const value of values) {
176073
176069
  if (!allowed.includes(value)) {
176074
176070
  diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
@@ -177779,9 +177775,6 @@ function duplicateLinkDiagnostic(source, target, location, range) {
177779
177775
  // src/scan/code/typescript.ts
177780
177776
  var typeScriptAdapter = {
177781
177777
  language: "typescript",
177782
- scanFile(filePath, content, options) {
177783
- return scanTypeScript(filePath, content, options);
177784
- },
177785
177778
  scanFiles(files, options) {
177786
177779
  return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
177787
177780
  }
@@ -178250,12 +178243,9 @@ function firstToken(text) {
178250
178243
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
178251
178244
  import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
178252
178245
 
178253
- // src/scan/code/worker/runtime-probe.ts
178254
- import { spawnSync as spawnSync2 } from "node:child_process";
178255
-
178256
178246
  // src/scan/code/worker/scanner-worker.ts
178257
178247
  var import__2020 = __toESM(require_2020(), 1);
178258
- import { spawn, spawnSync } from "node:child_process";
178248
+ import { spawn } from "node:child_process";
178259
178249
  import { mkdirSync } from "node:fs";
178260
178250
  import { tmpdir } from "node:os";
178261
178251
  import { dirname, join as join4 } from "node:path";
@@ -178454,14 +178444,12 @@ var scanner_worker_schema_default = {
178454
178444
  // src/scan/code/worker/scanner-worker.ts
178455
178445
  var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
178456
178446
  var KILLED_WORKER_GRACE_MS = 500;
178457
- function createLazyWorkerResponseValidator(compile) {
178458
- let validator;
178459
- return () => {
178460
- validator ??= compile();
178461
- return validator;
178462
- };
178447
+ var liveWorkers = new Map;
178448
+ var compiledResponseValidator;
178449
+ function responseValidator() {
178450
+ compiledResponseValidator ??= compileWorkerResponseValidator();
178451
+ return compiledResponseValidator;
178463
178452
  }
178464
- var responseValidator = createLazyWorkerResponseValidator(compileWorkerResponseValidator);
178465
178453
  function compileWorkerResponseValidator() {
178466
178454
  const ajv = new import__2020.default({ allErrors: true, strict: true });
178467
178455
  ajv.addSchema(common_output_schema_default);
@@ -178472,13 +178460,10 @@ function compileWorkerResponseValidator() {
178472
178460
  });
178473
178461
  }
178474
178462
  function invokeScannerWorker(request, command, run = runScannerWorkerProcess, stripEnv = []) {
178475
- return interpretWorkerProcess(request, command, run(processInput(request, command, stripEnv)));
178476
- }
178477
- function invokeScannerWorkerAsync(request, command, runAsync = runScannerWorkerProcessAsync, stripEnv = []) {
178478
- const run = runAsync(processInput(request, command, stripEnv));
178463
+ const task = run(processInput(request, command, stripEnv));
178479
178464
  return {
178480
- promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178481
- cancel: () => run.cancel()
178465
+ promise: task.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178466
+ cancel: () => task.cancel()
178482
178467
  };
178483
178468
  }
178484
178469
  function processInput(request, command, stripEnv) {
@@ -178554,36 +178539,6 @@ function workerTimeoutMs(fileCount) {
178554
178539
  return 30000 + 1000 * fileCount;
178555
178540
  }
178556
178541
  function runScannerWorkerProcess(input) {
178557
- const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178558
- try {
178559
- const [executable = "", ...args] = input.command;
178560
- const result = spawnSync(executable, args, {
178561
- env: workerProcessEnv(input.stripEnv),
178562
- input: input.stdin,
178563
- encoding: "utf8",
178564
- maxBuffer: maxOutputBytes,
178565
- killSignal: "SIGKILL",
178566
- ...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
178567
- });
178568
- return syncWorkerProcessResult(result, input.timeoutMs, maxOutputBytes);
178569
- } catch (error) {
178570
- return { ok: false, kind: "start", error, stderr: "" };
178571
- }
178572
- }
178573
- function syncWorkerProcessResult(result, timeoutMs, maxOutputBytes) {
178574
- const stderr = result.stderr ?? "";
178575
- if (result.error !== undefined) {
178576
- return syncSpawnFailure(result.error, workerStarted(result), timeoutMs, maxOutputBytes, stderr);
178577
- }
178578
- if (Buffer.byteLength(result.stdout ?? "") + Buffer.byteLength(stderr) > maxOutputBytes) {
178579
- return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178580
- }
178581
- if (typeof result.status !== "number") {
178582
- return { ok: false, kind: "execution", error: signalError(result.signal), stderr };
178583
- }
178584
- return { ok: true, exitCode: result.status, stdout: result.stdout ?? "", stderr };
178585
- }
178586
- function runScannerWorkerProcessAsync(input) {
178587
178542
  const run = deferred();
178588
178543
  let settled = false;
178589
178544
  let timer;
@@ -178593,6 +178548,9 @@ function runScannerWorkerProcessAsync(input) {
178593
178548
  return;
178594
178549
  }
178595
178550
  settled = true;
178551
+ if (child !== undefined) {
178552
+ liveWorkers.delete(child);
178553
+ }
178596
178554
  clearTimeout(timer);
178597
178555
  clearTimeout(grace);
178598
178556
  complete();
@@ -178601,24 +178559,34 @@ function runScannerWorkerProcessAsync(input) {
178601
178559
  let child;
178602
178560
  try {
178603
178561
  const [executable = "", ...args] = input.command;
178604
- child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
178562
+ child = spawn(executable, args, {
178563
+ env: workerProcessEnv(input.stripEnv),
178564
+ detached: ownGroup,
178565
+ windowsHide: true
178566
+ });
178605
178567
  } catch (error) {
178606
178568
  settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
178607
178569
  return { promise: run.promise, cancel: () => {
178608
178570
  return;
178609
178571
  } };
178610
178572
  }
178573
+ liveWorkers.set(child, ownGroup);
178611
178574
  const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178612
178575
  const budget = { bytes: 0, limit: maxOutputBytes };
178613
178576
  const stdout = new OutputCollector(budget);
178614
178577
  const stderr = new OutputCollector(budget);
178615
178578
  let failure;
178616
- const kill = (reason) => {
178579
+ let cause;
178580
+ let exited = false;
178581
+ const kill = (reason, why) => {
178617
178582
  failure ??= reason;
178618
- killWorker(child, ownGroup);
178583
+ cause ??= why;
178584
+ if (!exited) {
178585
+ killWorker(child, ownGroup);
178586
+ }
178619
178587
  grace ??= setTimeout(() => {
178620
178588
  closeStreams(child);
178621
- settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
178589
+ settle(() => run.resolve(closedProcessResult(null, null, failure, cause, stdout, stderr)));
178622
178590
  }, KILLED_WORKER_GRACE_MS);
178623
178591
  };
178624
178592
  let spawned = false;
@@ -178634,30 +178602,36 @@ function runScannerWorkerProcessAsync(input) {
178634
178602
  });
178635
178603
  child.stdout.on("data", (chunk) => {
178636
178604
  if (!stdout.add(chunk)) {
178637
- kill(outputLimitError(maxOutputBytes));
178605
+ kill(outputLimitError(maxOutputBytes), { type: "output-limit" });
178638
178606
  }
178639
178607
  });
178640
178608
  child.stderr.on("data", (chunk) => {
178641
178609
  if (!stderr.add(chunk)) {
178642
- kill(outputLimitError(maxOutputBytes));
178610
+ kill(outputLimitError(maxOutputBytes), { type: "output-limit" });
178643
178611
  }
178644
178612
  });
178645
178613
  child.stdin.on("error", () => {
178646
178614
  return;
178647
178615
  });
178616
+ child.on("exit", () => {
178617
+ exited = true;
178618
+ liveWorkers.delete(child);
178619
+ });
178648
178620
  child.on("close", (code, signal) => {
178649
- settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
178621
+ settle(() => run.resolve(closedProcessResult(code, signal, failure, cause, stdout, stderr)));
178650
178622
  });
178651
178623
  child.stdin.end(input.stdin);
178652
178624
  const { timeoutMs } = input;
178653
178625
  if (timeoutMs !== undefined) {
178654
- timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
178626
+ timer = setTimeout(() => kill(timeoutError(timeoutMs), { type: "timeout" }), timeoutMs);
178655
178627
  }
178656
178628
  return {
178657
178629
  promise: run.promise,
178658
178630
  cancel() {
178659
178631
  settle(() => {
178660
- killWorker(child, ownGroup);
178632
+ if (!exited) {
178633
+ killWorker(child, ownGroup);
178634
+ }
178661
178635
  closeStreams(child);
178662
178636
  run.reject(abortError());
178663
178637
  });
@@ -178697,39 +178671,36 @@ class OutputCollector {
178697
178671
  return Buffer.concat(this.chunks).toString("utf8");
178698
178672
  }
178699
178673
  }
178700
- function closedProcessResult(code, signal, failure, stdout, stderr) {
178674
+ function closedProcessResult(code, signal, failure, cause, stdout, stderr) {
178701
178675
  if (failure !== undefined) {
178702
- return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
178676
+ return {
178677
+ ok: false,
178678
+ kind: "execution",
178679
+ error: failure,
178680
+ ...cause === undefined ? {} : { cause },
178681
+ stderr: stderr.text()
178682
+ };
178703
178683
  }
178704
178684
  if (code === null) {
178705
- return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
178685
+ return {
178686
+ ok: false,
178687
+ kind: "execution",
178688
+ error: signalError(signal),
178689
+ cause: { type: "signal", signal: signal ?? "unknown" },
178690
+ stderr: stderr.text()
178691
+ };
178706
178692
  }
178707
178693
  return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
178708
178694
  }
178709
- function workerStarted(result) {
178710
- return typeof result.status === "number" || typeof result.signal === "string" || typeof result.pid === "number" && result.pid > 0;
178711
- }
178712
- function syncSpawnFailure(error, started, timeoutMs, maxOutputBytes, stderr) {
178713
- const code = error.code;
178714
- if (code === "ETIMEDOUT") {
178715
- return { ok: false, kind: "execution", error: timeoutError(timeoutMs ?? 0), stderr };
178716
- }
178717
- if (code === "ENOBUFS") {
178718
- return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
178719
- }
178720
- return { ok: false, kind: started ? "execution" : "start", error, stderr };
178721
- }
178722
178695
  function signalError(signal) {
178723
178696
  const name = signal ?? "unknown";
178724
- return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
178697
+ return new Error(`worker terminated by signal ${name}`);
178725
178698
  }
178726
178699
  function timeoutError(timeoutMs) {
178727
- return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
178728
- code: "ETIMEDOUT"
178729
- });
178700
+ return new Error(`worker timed out after ${timeoutMs} ms`);
178730
178701
  }
178731
178702
  function outputLimitError(maxOutputBytes) {
178732
- return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
178703
+ return new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`);
178733
178704
  }
178734
178705
  function validateWorkerResponse(value, request) {
178735
178706
  const validator = responseValidator();
@@ -178757,18 +178728,7 @@ function formatSchemaError(errors) {
178757
178728
  return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
178758
178729
  }
178759
178730
  function responseFilesMatchRequest(responseFiles, requestFiles) {
178760
- if (responseFiles.length !== requestFiles.length) {
178761
- return false;
178762
- }
178763
- return responseFiles.every((file, index) => {
178764
- if (!isRecord(file)) {
178765
- return false;
178766
- }
178767
- return file.filePath === requestFiles[index]?.filePath;
178768
- });
178769
- }
178770
- function isRecord(value) {
178771
- return typeof value === "object" && value !== null && !Array.isArray(value);
178731
+ return responseFiles.length === requestFiles.length && responseFiles.every((file, index) => file.filePath === requestFiles[index]?.filePath);
178772
178732
  }
178773
178733
  function scannerUnavailableDiagnostic(language, error, executable) {
178774
178734
  const label = languageLabel(language);
@@ -178807,20 +178767,17 @@ function scannerFailedDiagnostic(language, reason) {
178807
178767
  function languageLabel(language) {
178808
178768
  return language.charAt(0).toUpperCase() + language.slice(1);
178809
178769
  }
178770
+ function killRunningWorkers() {
178771
+ for (const [child, ownGroup] of liveWorkers) {
178772
+ killWorker(child, ownGroup);
178773
+ }
178774
+ }
178810
178775
 
178811
178776
  // src/scan/code/worker/runtime-probe.ts
178812
178777
  var PROBE_TIMEOUT_MS = 1e4;
178813
178778
  var PROBE_MAX_OUTPUT_BYTES = 64 * 1024;
178814
- function probeRuntime(command, stripEnv, spawn2 = spawnRuntimeProbe) {
178815
- const [executable = "", ...args] = command;
178816
- return classifyProbe(spawn2(executable, [...args, "--probe"], {
178817
- env: workerProcessEnv(stripEnv),
178818
- timeout: PROBE_TIMEOUT_MS,
178819
- maxBuffer: PROBE_MAX_OUTPUT_BYTES
178820
- }));
178821
- }
178822
- function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcessAsync) {
178823
- const run = runAsync({
178779
+ function probeRuntime(command, stripEnv, run = runScannerWorkerProcess) {
178780
+ const task = run({
178824
178781
  command: [...command, "--probe"],
178825
178782
  stdin: "",
178826
178783
  stripEnv,
@@ -178828,49 +178785,34 @@ function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcess
178828
178785
  maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
178829
178786
  });
178830
178787
  return {
178831
- promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
178832
- cancel: () => run.cancel()
178788
+ promise: task.promise.then(classifyProbe),
178789
+ cancel: () => task.cancel()
178833
178790
  };
178834
178791
  }
178835
- function spawnResultOf(result) {
178836
- if (result.ok) {
178837
- return { status: result.exitCode, signal: null, stdout: result.stdout, stderr: result.stderr };
178838
- }
178839
- const error = result.error instanceof Error ? result.error : new Error(reasonOf(result.error));
178840
- const { signal } = error;
178841
- if (typeof signal === "string") {
178842
- return { status: null, signal, stdout: "", stderr: result.stderr };
178843
- }
178844
- return { status: null, signal: null, stdout: "", stderr: result.stderr, error };
178845
- }
178846
178792
  function classifyProbe(result) {
178847
- if (result.error !== undefined) {
178848
- return classifySpawnError(result.error);
178849
- }
178850
- if (result.status === null) {
178851
- return { kind: "failed", reason: `probe terminated by signal ${result.signal ?? "unknown"}` };
178852
- }
178853
- if (result.status !== 0) {
178854
- const detail = firstLine(result.stderr);
178855
- return {
178856
- kind: "failed",
178857
- reason: `probe exited with status ${result.status}${detail === "" ? "" : `: ${detail}`}`
178858
- };
178859
- }
178860
- return parseProbeOutput(result.stdout);
178861
- }
178862
- function classifySpawnError(error) {
178863
- const code = error.code;
178864
- if (code === "ETIMEDOUT") {
178865
- return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
178793
+ if (result.ok) {
178794
+ if (result.exitCode !== 0) {
178795
+ const detail = firstLine(result.stderr);
178796
+ return {
178797
+ kind: "failed",
178798
+ reason: `probe exited with status ${result.exitCode}${detail === "" ? "" : `: ${detail}`}`
178799
+ };
178800
+ }
178801
+ return parseProbeOutput(result.stdout);
178866
178802
  }
178867
- if (code === "ENOBUFS") {
178868
- return {
178869
- kind: "failed",
178870
- reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178871
- };
178803
+ switch (result.cause?.type) {
178804
+ case "timeout":
178805
+ return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
178806
+ case "output-limit":
178807
+ return {
178808
+ kind: "failed",
178809
+ reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178810
+ };
178811
+ case "signal":
178812
+ return { kind: "failed", reason: `probe terminated by signal ${result.cause.signal}` };
178813
+ default:
178814
+ return { kind: "unstartable", reason: reasonOf(result.error) };
178872
178815
  }
178873
- return { kind: "unstartable", reason: reasonOf(error) };
178874
178816
  }
178875
178817
  function parseProbeOutput(stdout) {
178876
178818
  const text = stdout.trim();
@@ -178880,7 +178822,7 @@ function parseProbeOutput(stdout) {
178880
178822
  } catch {
178881
178823
  return malformed(text);
178882
178824
  }
178883
- if (!isRecord2(value)) {
178825
+ if (!isRecord(value)) {
178884
178826
  return malformed(text);
178885
178827
  }
178886
178828
  if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
@@ -178898,40 +178840,9 @@ function malformed(text) {
178898
178840
  function firstLine(text) {
178899
178841
  return text.trim().split(/\r?\n/u)[0] ?? "";
178900
178842
  }
178901
- function isRecord2(value) {
178843
+ function isRecord(value) {
178902
178844
  return typeof value === "object" && value !== null && !Array.isArray(value);
178903
178845
  }
178904
- function spawnRuntimeProbe(executable, args, options) {
178905
- try {
178906
- const result = spawnSync2(executable, args, {
178907
- env: options.env,
178908
- timeout: options.timeout,
178909
- killSignal: "SIGKILL",
178910
- maxBuffer: options.maxBuffer,
178911
- input: "",
178912
- encoding: "utf8",
178913
- windowsHide: true
178914
- });
178915
- const stdout = result.stdout ?? "";
178916
- const stderr = result.stderr ?? "";
178917
- const error = result.error === undefined && Buffer.byteLength(stdout) + Buffer.byteLength(stderr) > options.maxBuffer ? Object.assign(new Error("probe output exceeded maxBuffer"), { code: "ENOBUFS" }) : result.error;
178918
- return {
178919
- status: result.status,
178920
- signal: result.signal,
178921
- stdout,
178922
- stderr,
178923
- ...error === undefined ? {} : { error }
178924
- };
178925
- } catch (error) {
178926
- return {
178927
- status: null,
178928
- signal: null,
178929
- stdout: "",
178930
- stderr: "",
178931
- error: error instanceof Error ? error : new Error(String(error))
178932
- };
178933
- }
178934
- }
178935
178846
 
178936
178847
  // src/scan/code/worker/scanner-executable.ts
178937
178848
  import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
@@ -179143,22 +179054,12 @@ function rememberProbe(key, generation, outcome) {
179143
179054
  function resolveRuntimeWorkerCommand(language, options) {
179144
179055
  const env = options.env ?? process.env;
179145
179056
  const probe = options.probe ?? probeRuntime;
179146
- const steps = resolutionSteps(language, options, env);
179147
- let step = steps.next();
179148
- while (step.done !== true) {
179149
- step = steps.next(cachedProbe(step.value, env, probe));
179150
- }
179151
- return step.value;
179152
- }
179153
- function resolveRuntimeWorkerCommandAsync(language, options) {
179154
- const env = options.env ?? process.env;
179155
- const probeAsync = options.probeAsync ?? probeRuntimeAsync;
179156
179057
  return cancelableSequence(async (run) => {
179157
179058
  const steps = resolutionSteps(language, options, env);
179158
179059
  let step = steps.next();
179159
179060
  while (step.done !== true) {
179160
179061
  const request = step.value;
179161
- step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
179062
+ step = steps.next(await run(() => cachedProbe(request, env, probe)));
179162
179063
  }
179163
179064
  return step.value;
179164
179065
  });
@@ -179266,24 +179167,13 @@ function isExecutableFile(path, platform) {
179266
179167
  }
179267
179168
  }
179268
179169
  function cachedProbe({ command, stripEnv }, env, probe) {
179269
- const key = probeCacheKey(command, env);
179270
- const cached = probeCache.get(key);
179271
- if (cached !== undefined) {
179272
- return cached;
179273
- }
179274
- const generation = probeCacheGeneration;
179275
- const outcome = probe(command, stripEnv);
179276
- rememberProbe(key, generation, outcome);
179277
- return outcome;
179278
- }
179279
- function cachedProbeAsync({ command, stripEnv }, env, probeAsync) {
179280
179170
  const key = probeCacheKey(command, env);
179281
179171
  const cached = probeCache.get(key);
179282
179172
  if (cached !== undefined) {
179283
179173
  return settledCancelable(cached);
179284
179174
  }
179285
179175
  const generation = probeCacheGeneration;
179286
- return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
179176
+ return mapCancelable(probe(command, stripEnv), (outcome) => {
179287
179177
  rememberProbe(key, generation, outcome);
179288
179178
  return outcome;
179289
179179
  });
@@ -179339,16 +179229,12 @@ function failure(spec, language, code, reason) {
179339
179229
 
179340
179230
  // src/scan/code/dispatch.ts
179341
179231
  function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
179342
- const { commandAsync } = adapterOptions;
179343
- const bound = (context) => boundWorkerAdapter(language, workerCommand(command(context)), adapterOptions).adapter;
179344
- const prepare = (context) => mapCancelable(commandAsync?.(context) ?? settledCancelable(command(context)), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
179232
+ const resolve3 = (context) => typeof command === "function" ? settledCancelable(command(context)) : command.commandAsync(context);
179233
+ const prepare = (context) => mapCancelable(resolve3(context), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
179345
179234
  const scanBatchAsync = (files, options, context) => {
179346
179235
  if (files.length === 0) {
179347
179236
  return settledCancelable([]);
179348
179237
  }
179349
- if (commandAsync === undefined) {
179350
- return bound(context).scanFilesAsync(files, options, context);
179351
- }
179352
179238
  return cancelableSequence(async (step) => {
179353
179239
  const { adapter } = await step(() => prepare(context));
179354
179240
  return step(() => adapter.scanFilesAsync(files, options, context));
@@ -179356,8 +179242,6 @@ function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
179356
179242
  };
179357
179243
  return {
179358
179244
  language,
179359
- scanFile: (filePath, content, options, context) => bound(context).scanFile(filePath, content, options, context),
179360
- scanFiles: (files, options, context) => files.length === 0 ? [] : bound(context).scanFiles(files, options, context),
179361
179245
  scanFilesAsync: scanBatchAsync,
179362
179246
  prepare
179363
179247
  };
@@ -179367,20 +179251,6 @@ function boundWorkerAdapter(language, resolved, adapterOptions) {
179367
179251
  const runtime = resolved.ok ? resolved.runtime : [];
179368
179252
  const adapter = {
179369
179253
  language,
179370
- scanFile(filePath, content, options, context) {
179371
- const [scan] = adapter.scanFiles([{ filePath, content }], options, context);
179372
- return scan ?? emptyScan(language, filePath);
179373
- },
179374
- scanFiles(files, options, context) {
179375
- if (files.length === 0) {
179376
- return [];
179377
- }
179378
- if (!resolved.ok) {
179379
- return failedBatch(language, files, resolved.diagnostic);
179380
- }
179381
- const result = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
179382
- return result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic);
179383
- },
179384
179254
  scanFilesAsync(files, options, context) {
179385
179255
  if (files.length === 0) {
179386
179256
  return settledCancelable([]);
@@ -179388,36 +179258,28 @@ function boundWorkerAdapter(language, resolved, adapterOptions) {
179388
179258
  if (!resolved.ok) {
179389
179259
  return settledCancelable(failedBatch(language, files, resolved.diagnostic));
179390
179260
  }
179391
- const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
179261
+ const task = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
179392
179262
  return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
179393
179263
  },
179394
179264
  prepare: () => settledCancelable({ argv, runtime, adapter })
179395
179265
  };
179396
179266
  return { argv, runtime, adapter };
179397
179267
  }
179398
- function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
179399
- const plan = planCodeScan(files, codeInclude, read, options.onContent);
179400
- const context = scanContext(projectRoot, options);
179401
- for (const batch of plan.batches) {
179402
- const adapter = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179403
- fillBatch(plan, batch, adapter.scanFiles(batch.files, batch.options, context));
179404
- }
179405
- return assemblePlan(plan);
179406
- }
179407
179268
  function scanContext(projectRoot, options) {
179408
179269
  return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
179409
179270
  }
179410
- function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
179271
+ function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
179411
179272
  const plan = planCodeScan(files, codeInclude, read, options.onContent);
179412
179273
  const context = scanContext(projectRoot, options);
179413
179274
  return cancelableSequence(async (step) => {
179414
179275
  const cache = new Map;
179415
179276
  for (const batch of plan.batches) {
179416
179277
  const base = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
179417
- const prepare = base.prepare?.bind(base);
179418
- const { argv, runtime, adapter } = prepare === undefined ? { argv: [], adapter: base } : await step(() => prepare(context));
179419
- const keys = batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
179420
- const cached = keys.map((key) => options.cache?.get(key));
179278
+ const { argv, runtime, adapter } = "scanFilesAsync" in base ? await step(() => base.prepare(context)) : { argv: [], adapter: base };
179279
+ const keys = options.cache === undefined ? [] : batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
179280
+ const cached = options.cache === undefined ? batch.files.map(() => {
179281
+ return;
179282
+ }) : keys.map((key) => options.cache?.get(key));
179421
179283
  const missing = batch.files.filter((_, index) => cached[index] === undefined);
179422
179284
  const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
179423
179285
  let next = 0;
@@ -179425,7 +179287,7 @@ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {})
179425
179287
  fillBatch(plan, batch, results);
179426
179288
  results.forEach((scan, index) => {
179427
179289
  const key = keys[index];
179428
- if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
179290
+ if (options.cache !== undefined && scan !== undefined && key !== undefined && isReusableScan(scan)) {
179429
179291
  cache.set(key, scan);
179430
179292
  }
179431
179293
  });
@@ -179434,7 +179296,7 @@ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {})
179434
179296
  });
179435
179297
  }
179436
179298
  function scanFilesAsync(adapter, files, options, context) {
179437
- return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
179299
+ return "scanFilesAsync" in adapter ? adapter.scanFilesAsync(files, options, context) : settledCancelable(adapter.scanFiles(files, options, context));
179438
179300
  }
179439
179301
  var builtInAdapters = {
179440
179302
  typescript: typeScriptAdapter,
@@ -179452,7 +179314,9 @@ function runtimeWorkerAdapter(language) {
179452
179314
  const command = scanners?.[language]?.command;
179453
179315
  return command === undefined ? { projectRoot } : { projectRoot, command };
179454
179316
  };
179455
- return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
179317
+ return createScannerWorkerAdapter(language, {
179318
+ commandAsync: (context) => resolveRuntimeWorkerCommand(language, resolution(context))
179319
+ });
179456
179320
  }
179457
179321
  function planCodeScan(files, codeInclude, read, onContent) {
179458
179322
  const slots = [];
@@ -179773,37 +179637,28 @@ function flushDanglingWith(pending, diagnostics, message) {
179773
179637
  // src/query/project-scan.ts
179774
179638
  function scanProject(options) {
179775
179639
  const loaded = loadProjectScan(options, options.keepContent === true);
179776
- if (!loaded.ok) {
179777
- return loaded;
179778
- }
179779
- const { inputs } = loaded;
179780
- const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, codeScanOptions(inputs, options));
179781
- const docReads = readDocFiles(inputs, options);
179782
- return { ok: true, scan: finishProjectScan(inputs, codeScan, docReads, options.buildGraph) };
179783
- }
179784
- function scanProjectAsync(options) {
179785
- const loaded = loadProjectScan(options, true);
179786
179640
  if (!loaded.ok) {
179787
179641
  return cancelableSequence(async () => loaded);
179788
179642
  }
179789
179643
  const { inputs } = loaded;
179790
- const fingerprint = JSON.stringify(inputs.config);
179791
- const sameConfiguration = options.cache.fingerprint === fingerprint;
179792
- if (!sameConfiguration && options.cache.fingerprint !== "") {
179644
+ const previousCache = options.cache;
179645
+ const fingerprint = previousCache === undefined ? undefined : JSON.stringify(inputs.config);
179646
+ const sameConfiguration = previousCache !== undefined && previousCache.fingerprint === fingerprint;
179647
+ if (previousCache !== undefined && !sameConfiguration && previousCache.fingerprint !== "") {
179793
179648
  options.onConfigurationChange?.();
179794
179649
  }
179795
- const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179650
+ const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179796
179651
  ...codeScanOptions(inputs, options),
179797
- ...sameConfiguration ? { cache: options.cache.entries } : {}
179652
+ ...previousCache === undefined ? {} : { cache: sameConfiguration ? previousCache.entries : new Map }
179798
179653
  });
179799
179654
  const docReads = readDocFiles(inputs, options);
179800
179655
  return cancelableSequence(async (step) => {
179801
179656
  const { cache, ...code } = await step(codeScan);
179802
- const scan = finishProjectScan(inputs, code, docReads, true);
179657
+ const scan = finishProjectScan(inputs, code, docReads, options.buildGraph);
179803
179658
  return {
179804
179659
  ok: true,
179805
179660
  scan,
179806
- cache: { fingerprint, entries: cache }
179661
+ cache: { fingerprint: fingerprint ?? "", entries: cache }
179807
179662
  };
179808
179663
  });
179809
179664
  }
@@ -179908,19 +179763,12 @@ class Project {
179908
179763
  this.overlay.delete(relPath);
179909
179764
  this.overlayRevision += 1;
179910
179765
  }
179911
- resolve() {
179912
- const outcome = scanProject({
179913
- ...this.scanSources(this.overlay),
179914
- buildGraph: true,
179915
- keepContent: true
179916
- });
179917
- this.current = resolvedState(outcome);
179918
- return this.current;
179919
- }
179920
179766
  resolveAsync() {
179921
179767
  const revision = this.overlayRevision;
179922
- const scan = scanProjectAsync({
179768
+ const scan = scanProject({
179923
179769
  ...this.scanSources(new Map(this.overlay)),
179770
+ buildGraph: true,
179771
+ keepContent: true,
179924
179772
  cache: this.cache,
179925
179773
  onConfigurationChange: clearRuntimeProbeCache
179926
179774
  });
@@ -180419,12 +180267,12 @@ function fullChangeText(changes) {
180419
180267
  }
180420
180268
 
180421
180269
  // src/query/check.ts
180422
- function check(options) {
180270
+ async function check(options) {
180423
180271
  const audit = options.audit ?? false;
180424
- const outcome = scanProject({
180272
+ const outcome = await scanProject({
180425
180273
  projectRoot: options.projectRoot,
180426
180274
  ...options.adapters === undefined ? {} : { adapters: options.adapters }
180427
- });
180275
+ }).promise;
180428
180276
  if (!outcome.ok) {
180429
180277
  const sorted = sortDiagnostics(outcome.diagnostics);
180430
180278
  return { diagnostics: sorted, summary: summarizeDiagnostics(sorted) };
@@ -180498,8 +180346,8 @@ function collectGateViolations(result) {
180498
180346
  }
180499
180347
  return violations;
180500
180348
  }
180501
- function related(options) {
180502
- const outcome = scanProject({ projectRoot: options.projectRoot, buildGraph: true });
180349
+ async function related(options) {
180350
+ const outcome = await scanProject({ projectRoot: options.projectRoot, buildGraph: true }).promise;
180503
180351
  if (!outcome.ok) {
180504
180352
  return { ok: false, diagnostics: outcome.diagnostics };
180505
180353
  }
@@ -180557,12 +180405,12 @@ function computeContext(graph, contentByFile, inputFiles) {
180557
180405
  summary: { inputFiles: inputSet.size, contexts: contexts.length }
180558
180406
  };
180559
180407
  }
180560
- function context(options) {
180561
- const outcome = scanProject({
180408
+ async function context(options) {
180409
+ const outcome = await scanProject({
180562
180410
  projectRoot: options.projectRoot,
180563
180411
  buildGraph: true,
180564
180412
  keepContent: true
180565
- });
180413
+ }).promise;
180566
180414
  if (!outcome.ok) {
180567
180415
  return { ok: false, diagnostics: outcome.diagnostics };
180568
180416
  }
@@ -180636,12 +180484,12 @@ function compareBlocks(left, right) {
180636
180484
  }
180637
180485
 
180638
180486
  // src/query/graph-output.ts
180639
- function graph(options) {
180640
- const outcome = scanProject({
180487
+ async function graph(options) {
180488
+ const outcome = await scanProject({
180641
180489
  projectRoot: options.projectRoot,
180642
180490
  buildGraph: true,
180643
180491
  keepContent: true
180644
- });
180492
+ }).promise;
180645
180493
  if (!outcome.ok) {
180646
180494
  return { ok: false, diagnostics: outcome.diagnostics };
180647
180495
  }
@@ -181136,13 +180984,6 @@ class CliError extends Error {
181136
180984
  }
181137
180985
  }
181138
180986
 
181139
- class InitCliError extends CliError {
181140
- constructor(message, guidance) {
181141
- super(message, guidance);
181142
- this.name = "InitCliError";
181143
- }
181144
- }
181145
-
181146
180987
  class DiagnosticOutputError extends Error {
181147
180988
  constructor(message) {
181148
180989
  super(message);
@@ -182101,7 +181942,6 @@ function readFileOrUndefined(path) {
182101
181942
  }
182102
181943
 
182103
181944
  // src/setup/init-plan.ts
182104
- var CONFIG_FILE_NAME2 = "docbridge.config.json";
182105
181945
  var INIT_SKILL_NAMES = ["docbridge"];
182106
181946
  var LEGACY_SKILL_NAMES = [
182107
181947
  "docbridge-adopt",
@@ -182269,7 +182109,7 @@ function planConfigOperations(input) {
182269
182109
  return [
182270
182110
  {
182271
182111
  action,
182272
- path: CONFIG_FILE_NAME2,
182112
+ path: CONFIG_FILE_NAME,
182273
182113
  content,
182274
182114
  reason: "Create docbridge.config.json from discovered scope."
182275
182115
  }
@@ -182421,7 +182261,7 @@ function resolveInitWithAgentTarget(discovery, options) {
182421
182261
  return { target: discovery.agent.defaultTarget, error: undefined };
182422
182262
  }
182423
182263
  function readExistingConfig(projectRoot) {
182424
- const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
182264
+ const configPath = join12(projectRoot, CONFIG_FILE_NAME);
182425
182265
  let rawText;
182426
182266
  try {
182427
182267
  rawText = readFileSync7(configPath, "utf8");
@@ -182499,7 +182339,7 @@ function parseInitOptions(args, command) {
182499
182339
  if (arg === "--root") {
182500
182340
  const root = args[index + 1];
182501
182341
  if (root === undefined) {
182502
- throw new InitCliError("--root requires a path.", rootPathGuidance(command));
182342
+ throw new CliError("--root requires a path.", rootPathGuidance(command));
182503
182343
  }
182504
182344
  options.root = root;
182505
182345
  index += 1;
@@ -182508,22 +182348,22 @@ function parseInitOptions(args, command) {
182508
182348
  if (arg === "--agent-target") {
182509
182349
  const target = args[index + 1];
182510
182350
  if (target === undefined) {
182511
- throw new InitCliError("--agent-target requires a value.", agentTargetGuidance(command));
182351
+ throw new CliError("--agent-target requires a value.", agentTargetGuidance(command));
182512
182352
  }
182513
182353
  if (!AGENT_TARGETS.has(target)) {
182514
- throw new InitCliError(`Unknown agent target: ${target}. Supported values: codex, claude, both${command === "init" ? ", none" : ""}.`, agentTargetGuidance(command));
182354
+ throw new CliError(`Unknown agent target: ${target}. Supported values: codex, claude, both${command === "init" ? ", none" : ""}.`, agentTargetGuidance(command));
182515
182355
  }
182516
182356
  if (command === "init-with-agent" && target === "none") {
182517
- throw new InitCliError("init-with-agent requires an agent target other than none.", agentTargetGuidance(command));
182357
+ throw new CliError("init-with-agent requires an agent target other than none.", agentTargetGuidance(command));
182518
182358
  }
182519
182359
  options.agentTarget = target;
182520
182360
  index += 1;
182521
182361
  continue;
182522
182362
  }
182523
182363
  if (arg.startsWith("--")) {
182524
- throw new InitCliError(`Unknown option: ${arg}`, commandHelpGuidance(command));
182364
+ throw new CliError(`Unknown option: ${arg}`, commandHelpGuidance(command));
182525
182365
  }
182526
- throw new InitCliError(`Unexpected argument: ${arg}`, commandHelpGuidance(command));
182366
+ throw new CliError(`Unexpected argument: ${arg}`, commandHelpGuidance(command));
182527
182367
  }
182528
182368
  return options;
182529
182369
  }
@@ -182610,19 +182450,19 @@ function resolveConfirmedScope(input) {
182610
182450
  };
182611
182451
  }
182612
182452
  if (!input.prompts.isInteractive) {
182613
- throw new InitCliError("Interactive setup requires a TTY. Re-run with --yes for non-interactive mode.");
182453
+ throw new CliError("Interactive setup requires a TTY. Re-run with --yes for non-interactive mode.");
182614
182454
  }
182615
182455
  if (input.discovery.docs.ambiguous) {
182616
182456
  const choices = input.discovery.docs.candidates.map((candidate) => candidate.pattern);
182617
182457
  if (choices.length === 0) {
182618
- throw new InitCliError("Docs scope is ambiguous and no candidates were detected. Create docbridge.config.json manually.");
182458
+ throw new CliError("Docs scope is ambiguous and no candidates were detected. Create docbridge.config.json manually.");
182619
182459
  }
182620
182460
  const docsPattern2 = input.prompts.select("Choose docs scope:", choices, choices[0] ?? "");
182621
182461
  const languages2 = selectLanguages(input.discovery.code.languages, input.prompts);
182622
182462
  return { docsPattern: docsPattern2, languages: languages2 };
182623
182463
  }
182624
182464
  if (input.discovery.docs.recommended === undefined) {
182625
- throw new InitCliError("Docs scope could not be determined.");
182465
+ throw new CliError("Docs scope could not be determined.");
182626
182466
  }
182627
182467
  let docsPattern = input.discovery.docs.recommended.pattern;
182628
182468
  if (!input.prompts.confirm(`Use docs scope ${docsPattern}?`, true)) {
@@ -182656,7 +182496,7 @@ function selectLanguages(languages, prompts) {
182656
182496
  }
182657
182497
  }
182658
182498
  if (selected.length === 0) {
182659
- throw new InitCliError("At least one supported code language must remain in scope.");
182499
+ throw new CliError("At least one supported code language must remain in scope.");
182660
182500
  }
182661
182501
  return selected;
182662
182502
  }
@@ -182686,10 +182526,10 @@ function resolveProjectRoot(root, command) {
182686
182526
  try {
182687
182527
  stats = statSync4(projectRoot);
182688
182528
  } catch {
182689
- throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182529
+ throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182690
182530
  }
182691
182531
  if (!stats.isDirectory()) {
182692
- throw new InitCliError(`Root path is not a directory: ${root}`, rootPathGuidance(command));
182532
+ throw new CliError(`Root path is not a directory: ${root}`, rootPathGuidance(command));
182693
182533
  }
182694
182534
  return projectRoot;
182695
182535
  }
@@ -182965,8 +182805,7 @@ function planUpgrade(input) {
182965
182805
  pending,
182966
182806
  messages,
182967
182807
  nextSteps: buildNextSteps({ mode, cli, options: input.options, pending }),
182968
- requiresConfirmation: mode === "apply" && !input.options.dryRun && !input.options.yes && operations.some((operation) => operation.action === "overwrite" || operation.action === "remove"),
182969
- exitCode: 0
182808
+ requiresConfirmation: mode === "apply" && !input.options.dryRun && !input.options.yes && operations.some((operation) => operation.action === "overwrite" || operation.action === "remove")
182970
182809
  };
182971
182810
  }
182972
182811
  function planOperations(input) {
@@ -183149,7 +182988,7 @@ function runUpgrade(options, io, initRuntime, runtime) {
183149
182988
  });
183150
182989
  if (plan.mode === "check" || options.dryRun) {
183151
182990
  io.stdout(formatUpgradePlan(plan));
183152
- return plan.exitCode;
182991
+ return 0;
183153
182992
  }
183154
182993
  return applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime);
183155
182994
  }
@@ -183167,7 +183006,7 @@ function applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime) {
183167
183006
  applySkillOperation(projectRoot, packageRoot, operation);
183168
183007
  }
183169
183008
  io.stdout(formatUpgradePlan(plan));
183170
- return plan.exitCode;
183009
+ return 0;
183171
183010
  }
183172
183011
  function parseAgentTarget(value) {
183173
183012
  if (value === undefined) {
@@ -183192,6 +183031,31 @@ function resolveProjectRoot2(root) {
183192
183031
  return projectRoot;
183193
183032
  }
183194
183033
 
183034
+ // src/cli/worker-signals.ts
183035
+ async function killingWorkersOnSignal(scan) {
183036
+ const signals = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
183037
+ const handlers = new Map;
183038
+ const remove = () => {
183039
+ for (const [signal, handler] of handlers) {
183040
+ process.off(signal, handler);
183041
+ }
183042
+ };
183043
+ for (const signal of signals) {
183044
+ const handler = () => {
183045
+ killRunningWorkers();
183046
+ remove();
183047
+ process.kill(process.pid, signal);
183048
+ };
183049
+ handlers.set(signal, handler);
183050
+ process.on(signal, handler);
183051
+ }
183052
+ try {
183053
+ return await scan();
183054
+ } finally {
183055
+ remove();
183056
+ }
183057
+ }
183058
+
183195
183059
  // src/cli/index.ts
183196
183060
  var VERSION = package_default.version;
183197
183061
  function parseCheckOptions(args) {
@@ -183219,9 +183083,9 @@ function resolveProjectRoot3(root, command) {
183219
183083
  }
183220
183084
  return projectRoot;
183221
183085
  }
183222
- function runCheck(options, io) {
183086
+ async function runCheck(options, io) {
183223
183087
  const projectRoot = resolveProjectRoot3(options.root, "check");
183224
- const result = check({ projectRoot, audit: options.audit });
183088
+ const result = await killingWorkersOnSignal(() => check({ projectRoot, audit: options.audit }));
183225
183089
  if (options.json) {
183226
183090
  io.stdout(`${JSON.stringify(result, null, 2)}
183227
183091
  `);
@@ -183242,7 +183106,7 @@ See \`docbridge docs show troubleshooting\` for diagnostic codes and fixes.
183242
183106
  }
183243
183107
  return result.summary.errors > 0 ? 1 : 0;
183244
183108
  }
183245
- function runFileCommand(command, options, io, execute, render) {
183109
+ async function runFileCommand(command, options, io, execute, render) {
183246
183110
  const projectRoot = resolveProjectRoot3(options.root, command);
183247
183111
  const inputFiles = [...options.files];
183248
183112
  if (options.stdin) {
@@ -183250,7 +183114,7 @@ function runFileCommand(command, options, io, execute, render) {
183250
183114
  inputFiles.push(...readStdin().split(`
183251
183115
  `));
183252
183116
  }
183253
- const outcome = execute(projectRoot, inputFiles);
183117
+ const outcome = await killingWorkersOnSignal(async () => execute(projectRoot, inputFiles));
183254
183118
  if (!outcome.ok) {
183255
183119
  throw new DiagnosticOutputError(outcome.diagnostics.map(formatDiagnostic).join(`
183256
183120
  `));
@@ -183271,7 +183135,7 @@ function writeStructuredResult(result, options) {
183271
183135
  `);
183272
183136
  }
183273
183137
  }
183274
- function runRelated(options, io) {
183138
+ async function runRelated(options, io) {
183275
183139
  if (!options.stdin && options.files.length === 0) {
183276
183140
  throw new CliError("No input files were provided.", missingInputGuidance("related"));
183277
183141
  }
@@ -183300,7 +183164,7 @@ function runRelated(options, io) {
183300
183164
  return 0;
183301
183165
  });
183302
183166
  }
183303
- function runContext(options, io) {
183167
+ async function runContext(options, io) {
183304
183168
  if (!options.stdin && options.files.length === 0) {
183305
183169
  throw new CliError("No input files were provided.", missingInputGuidance("context"));
183306
183170
  }
@@ -183314,7 +183178,7 @@ function runContext(options, io) {
183314
183178
  return 0;
183315
183179
  });
183316
183180
  }
183317
- function runGraph(options, io) {
183181
+ async function runGraph(options, io) {
183318
183182
  return runFileCommand("graph", options, io, (projectRoot, inputFiles) => graph({ projectRoot, inputFiles, includeContent: options.includeContent }), (result, projectRoot, inputFiles) => {
183319
183183
  const normalizedInputFiles = normalizeChangedPaths(projectRoot, inputFiles);
183320
183184
  writeStructuredResult(result, {
@@ -183368,7 +183232,7 @@ var COMMAND_HANDLERS = {
183368
183232
  return 0;
183369
183233
  }
183370
183234
  };
183371
- function run(argv, io = {
183235
+ async function run(argv, io = {
183372
183236
  stdout: (text) => process.stdout.write(text),
183373
183237
  stderr: (text) => process.stderr.write(text)
183374
183238
  }, initRuntime = { prompts: createDefaultPrompts() }, cliRuntime = {}) {
@@ -183390,7 +183254,7 @@ function run(argv, io = {
183390
183254
  return 0;
183391
183255
  }
183392
183256
  if (isSubcommand(command)) {
183393
- const exitCode = COMMAND_HANDLERS[command](rest, io, initRuntime, cliRuntime);
183257
+ const exitCode = await COMMAND_HANDLERS[command](rest, io, initRuntime, cliRuntime);
183394
183258
  writeUpdateNotice(argv, io, initRuntime, cliRuntime);
183395
183259
  return exitCode;
183396
183260
  }
@@ -183455,7 +183319,7 @@ if (__require.main == __require.module) {
183455
183319
  const argv = process.argv.slice(2);
183456
183320
  const isTty = process.stderr.isTTY === true;
183457
183321
  const latest = await resolveLatestForInvocation(argv, process.env, isTty);
183458
- process.exitCode = run(argv, {
183322
+ process.exitCode = await run(argv, {
183459
183323
  stdout: (text) => process.stdout.write(text),
183460
183324
  stderr: (text) => process.stderr.write(text)
183461
183325
  }, { prompts: createDefaultPrompts() }, { ...latest !== undefined ? { latest } : {}, env: process.env, isTty });
package/docs/README.md CHANGED
@@ -63,7 +63,7 @@ with the specification for the interface whose exact contract you need:
63
63
  - [Commit messages](contributing/commits.md)
64
64
  - [Pull requests](contributing/pull-requests.md)
65
65
  - [Releasing](contributing/releasing.md)
66
- - [Self-audit baseline](contributing/self-audit.md)
66
+ - [Self-audit](contributing/self-audit.md)
67
67
 
68
68
  ## Historical records
69
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docbridge",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "A CLI that links Markdown specifications and code declarations. Runs on Node.js and Bun.",
5
5
  "keywords": [
6
6
  "bun",