docbridge 0.13.0 → 0.14.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.0] - 2026-10-08
11
+
12
+ ### Changed
13
+
14
+ - A heading anchor keeps `_`, as GitHub's heading slugs do. Before, `_` became
15
+ `-`, so `### title_definitions` gave `#title-definitions`; it now gives
16
+ `#title_definitions`. A link written with the old `-` form now produces
17
+ `doc_anchor_not_found`; change it to match the heading, such as
18
+ `#title_definitions`.
19
+
20
+ ## [0.13.1] - 2026-10-06
21
+
22
+ ### Fixed
23
+
24
+ - A worker that a scan started no longer outlives the CLI. On POSIX systems
25
+ each worker runs in its own process group, a time or output limit kills the
26
+ whole group, and `check`, `related`, `context`, and `graph` kill their
27
+ running workers when the CLI receives `SIGINT`, `SIGTERM`, or `SIGHUP`
28
+ during a scan, then end by that signal. Before, a worker that ran past the
29
+ CLI, or a process the worker started, could keep running.
30
+
10
31
  ## [0.13.0] - 2026-10-01
11
32
 
12
33
  ### Added
@@ -547,7 +568,9 @@ Initial release of the SpecLink CLI.
547
568
  - `speclink check` command with `--root`, `--json`, and `--audit` options.
548
569
  - `speclink --version` (alias `-v`) and `speclink --help` (alias `-h`).
549
570
 
550
- [Unreleased]: https://github.com/salan70/docbridge/compare/v0.13.0...HEAD
571
+ [Unreleased]: https://github.com/salan70/docbridge/compare/v0.14.0...HEAD
572
+ [0.14.0]: https://github.com/salan70/docbridge/releases/tag/v0.14.0
573
+ [0.13.1]: https://github.com/salan70/docbridge/releases/tag/v0.13.1
551
574
  [0.13.0]: https://github.com/salan70/docbridge/releases/tag/v0.13.0
552
575
  [0.12.0]: https://github.com/salan70/docbridge/releases/tag/v0.12.0
553
576
  [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
+ 2db42ddea8060f988cea4933696e73d652ae5b807a8e8ff00249375faf2b0be4 docbridge-swift-scanner
2
+ f3da86ea5a0c1c92fa1e1f077251bec490e294d257b09cd03a46c8ce382aad19 docbridge_dart_scanner
3
3
  7fc3e6842b3e1bf425b8fd63e241eee7e64875f16be7ba8d1ae4a25baa7f2338 docbridge-rust-scanner
4
- 1a7160cf70cb6ff1517d72685b6ecbfe1878ed1fed013fef706e5f2405ddbce0 docbridge-go-scanner
4
+ 1646b06856b0420acdcb82d1692b7afea83fe2b7666adeb6412c99782dfc8cae docbridge-go-scanner
@@ -1,4 +1,4 @@
1
- 6e78c1c79c16572ed0759bfcf24ddd4cc77c7d1d699d19dc0279a9abca38d248 docbridge-swift-scanner
1
+ 0c023cfacede26a217a62420f9da1206b93c228122a3685469685a3db89f7735 docbridge-swift-scanner
2
2
  b75b14006ca792ed3ca8faf7ceee9e4d33c895f27dd416b640818cf8a4bdd9c8 docbridge_dart_scanner
3
3
  e0da7b8cc6ee23704f5fe8514cc309f0bb60380c77b55e3bc5ed113abd1f3478 docbridge-rust-scanner
4
- af845c263f65cc5db5beb01b4f4f7251ce449391986b4e288c03abfa04f7e708 docbridge-go-scanner
4
+ 5b8aee5b85a38679875c473b2dc32f021f022cba242785bb0fd94ee6fda0a6bf 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: 0c023cfacede26a217a62420f9da1206b93c228122a3685469685a3db89f7735
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.14.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",
@@ -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(", ")}.`));
@@ -177601,24 +177597,13 @@ function manifestLink(source, target, entry, side) {
177601
177597
  }
177602
177598
 
177603
177599
  // src/shared/cancelable.ts
177604
- function deferred() {
177605
- let settle;
177606
- const promise = new Promise((resolve2, reject) => {
177607
- settle = { resolve: resolve2, reject };
177608
- });
177609
- return {
177610
- promise,
177611
- resolve: (value) => settle?.resolve(value),
177612
- reject: (error) => settle?.reject(error)
177613
- };
177614
- }
177615
177600
  function settledCancelable(value) {
177616
177601
  return { promise: Promise.resolve(value), cancel: () => {
177617
177602
  return;
177618
177603
  } };
177619
177604
  }
177620
177605
  function mapCancelable(task, map) {
177621
- const mapped = deferred();
177606
+ const mapped = Promise.withResolvers();
177622
177607
  let cancelled = false;
177623
177608
  task.promise.then((value) => {
177624
177609
  if (cancelled) {
@@ -177644,7 +177629,7 @@ function isAbortError(error) {
177644
177629
  return error instanceof Error && error.name === "AbortError";
177645
177630
  }
177646
177631
  function cancelableSequence(body) {
177647
- const outcome = deferred();
177632
+ const outcome = Promise.withResolvers();
177648
177633
  let cancelled = false;
177649
177634
  let running;
177650
177635
  const step = async (next) => {
@@ -177779,9 +177764,6 @@ function duplicateLinkDiagnostic(source, target, location, range) {
177779
177764
  // src/scan/code/typescript.ts
177780
177765
  var typeScriptAdapter = {
177781
177766
  language: "typescript",
177782
- scanFile(filePath, content, options) {
177783
- return scanTypeScript(filePath, content, options);
177784
- },
177785
177767
  scanFiles(files, options) {
177786
177768
  return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
177787
177769
  }
@@ -178250,12 +178232,9 @@ function firstToken(text) {
178250
178232
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
178251
178233
  import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
178252
178234
 
178253
- // src/scan/code/worker/runtime-probe.ts
178254
- import { spawnSync as spawnSync2 } from "node:child_process";
178255
-
178256
178235
  // src/scan/code/worker/scanner-worker.ts
178257
178236
  var import__2020 = __toESM(require_2020(), 1);
178258
- import { spawn, spawnSync } from "node:child_process";
178237
+ import { spawn } from "node:child_process";
178259
178238
  import { mkdirSync } from "node:fs";
178260
178239
  import { tmpdir } from "node:os";
178261
178240
  import { dirname, join as join4 } from "node:path";
@@ -178454,14 +178433,12 @@ var scanner_worker_schema_default = {
178454
178433
  // src/scan/code/worker/scanner-worker.ts
178455
178434
  var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
178456
178435
  var KILLED_WORKER_GRACE_MS = 500;
178457
- function createLazyWorkerResponseValidator(compile) {
178458
- let validator;
178459
- return () => {
178460
- validator ??= compile();
178461
- return validator;
178462
- };
178436
+ var liveWorkers = new Map;
178437
+ var compiledResponseValidator;
178438
+ function responseValidator() {
178439
+ compiledResponseValidator ??= compileWorkerResponseValidator();
178440
+ return compiledResponseValidator;
178463
178441
  }
178464
- var responseValidator = createLazyWorkerResponseValidator(compileWorkerResponseValidator);
178465
178442
  function compileWorkerResponseValidator() {
178466
178443
  const ajv = new import__2020.default({ allErrors: true, strict: true });
178467
178444
  ajv.addSchema(common_output_schema_default);
@@ -178472,13 +178449,10 @@ function compileWorkerResponseValidator() {
178472
178449
  });
178473
178450
  }
178474
178451
  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));
178452
+ const task = run(processInput(request, command, stripEnv));
178479
178453
  return {
178480
- promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178481
- cancel: () => run.cancel()
178454
+ promise: task.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
178455
+ cancel: () => task.cancel()
178482
178456
  };
178483
178457
  }
178484
178458
  function processInput(request, command, stripEnv) {
@@ -178554,37 +178528,7 @@ function workerTimeoutMs(fileCount) {
178554
178528
  return 30000 + 1000 * fileCount;
178555
178529
  }
178556
178530
  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
- const run = deferred();
178531
+ const run = Promise.withResolvers();
178588
178532
  let settled = false;
178589
178533
  let timer;
178590
178534
  let grace;
@@ -178593,6 +178537,9 @@ function runScannerWorkerProcessAsync(input) {
178593
178537
  return;
178594
178538
  }
178595
178539
  settled = true;
178540
+ if (child !== undefined) {
178541
+ liveWorkers.delete(child);
178542
+ }
178596
178543
  clearTimeout(timer);
178597
178544
  clearTimeout(grace);
178598
178545
  complete();
@@ -178601,24 +178548,34 @@ function runScannerWorkerProcessAsync(input) {
178601
178548
  let child;
178602
178549
  try {
178603
178550
  const [executable = "", ...args] = input.command;
178604
- child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
178551
+ child = spawn(executable, args, {
178552
+ env: workerProcessEnv(input.stripEnv),
178553
+ detached: ownGroup,
178554
+ windowsHide: true
178555
+ });
178605
178556
  } catch (error) {
178606
178557
  settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
178607
178558
  return { promise: run.promise, cancel: () => {
178608
178559
  return;
178609
178560
  } };
178610
178561
  }
178562
+ liveWorkers.set(child, ownGroup);
178611
178563
  const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
178612
178564
  const budget = { bytes: 0, limit: maxOutputBytes };
178613
178565
  const stdout = new OutputCollector(budget);
178614
178566
  const stderr = new OutputCollector(budget);
178615
178567
  let failure;
178616
- const kill = (reason) => {
178568
+ let cause;
178569
+ let exited = false;
178570
+ const kill = (reason, why) => {
178617
178571
  failure ??= reason;
178618
- killWorker(child, ownGroup);
178572
+ cause ??= why;
178573
+ if (!exited) {
178574
+ killWorker(child, ownGroup);
178575
+ }
178619
178576
  grace ??= setTimeout(() => {
178620
178577
  closeStreams(child);
178621
- settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
178578
+ settle(() => run.resolve(closedProcessResult(null, null, failure, cause, stdout, stderr)));
178622
178579
  }, KILLED_WORKER_GRACE_MS);
178623
178580
  };
178624
178581
  let spawned = false;
@@ -178634,30 +178591,36 @@ function runScannerWorkerProcessAsync(input) {
178634
178591
  });
178635
178592
  child.stdout.on("data", (chunk) => {
178636
178593
  if (!stdout.add(chunk)) {
178637
- kill(outputLimitError(maxOutputBytes));
178594
+ kill(outputLimitError(maxOutputBytes), { type: "output-limit" });
178638
178595
  }
178639
178596
  });
178640
178597
  child.stderr.on("data", (chunk) => {
178641
178598
  if (!stderr.add(chunk)) {
178642
- kill(outputLimitError(maxOutputBytes));
178599
+ kill(outputLimitError(maxOutputBytes), { type: "output-limit" });
178643
178600
  }
178644
178601
  });
178645
178602
  child.stdin.on("error", () => {
178646
178603
  return;
178647
178604
  });
178605
+ child.on("exit", () => {
178606
+ exited = true;
178607
+ liveWorkers.delete(child);
178608
+ });
178648
178609
  child.on("close", (code, signal) => {
178649
- settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
178610
+ settle(() => run.resolve(closedProcessResult(code, signal, failure, cause, stdout, stderr)));
178650
178611
  });
178651
178612
  child.stdin.end(input.stdin);
178652
178613
  const { timeoutMs } = input;
178653
178614
  if (timeoutMs !== undefined) {
178654
- timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
178615
+ timer = setTimeout(() => kill(timeoutError(timeoutMs), { type: "timeout" }), timeoutMs);
178655
178616
  }
178656
178617
  return {
178657
178618
  promise: run.promise,
178658
178619
  cancel() {
178659
178620
  settle(() => {
178660
- killWorker(child, ownGroup);
178621
+ if (!exited) {
178622
+ killWorker(child, ownGroup);
178623
+ }
178661
178624
  closeStreams(child);
178662
178625
  run.reject(abortError());
178663
178626
  });
@@ -178697,39 +178660,36 @@ class OutputCollector {
178697
178660
  return Buffer.concat(this.chunks).toString("utf8");
178698
178661
  }
178699
178662
  }
178700
- function closedProcessResult(code, signal, failure, stdout, stderr) {
178663
+ function closedProcessResult(code, signal, failure, cause, stdout, stderr) {
178701
178664
  if (failure !== undefined) {
178702
- return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
178665
+ return {
178666
+ ok: false,
178667
+ kind: "execution",
178668
+ error: failure,
178669
+ ...cause === undefined ? {} : { cause },
178670
+ stderr: stderr.text()
178671
+ };
178703
178672
  }
178704
178673
  if (code === null) {
178705
- return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
178674
+ return {
178675
+ ok: false,
178676
+ kind: "execution",
178677
+ error: signalError(signal),
178678
+ cause: { type: "signal", signal: signal ?? "unknown" },
178679
+ stderr: stderr.text()
178680
+ };
178706
178681
  }
178707
178682
  return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
178708
178683
  }
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
178684
  function signalError(signal) {
178723
178685
  const name = signal ?? "unknown";
178724
- return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
178686
+ return new Error(`worker terminated by signal ${name}`);
178725
178687
  }
178726
178688
  function timeoutError(timeoutMs) {
178727
- return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
178728
- code: "ETIMEDOUT"
178729
- });
178689
+ return new Error(`worker timed out after ${timeoutMs} ms`);
178730
178690
  }
178731
178691
  function outputLimitError(maxOutputBytes) {
178732
- return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
178692
+ return new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`);
178733
178693
  }
178734
178694
  function validateWorkerResponse(value, request) {
178735
178695
  const validator = responseValidator();
@@ -178757,18 +178717,7 @@ function formatSchemaError(errors) {
178757
178717
  return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
178758
178718
  }
178759
178719
  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);
178720
+ return responseFiles.length === requestFiles.length && responseFiles.every((file, index) => file.filePath === requestFiles[index]?.filePath);
178772
178721
  }
178773
178722
  function scannerUnavailableDiagnostic(language, error, executable) {
178774
178723
  const label = languageLabel(language);
@@ -178807,20 +178756,17 @@ function scannerFailedDiagnostic(language, reason) {
178807
178756
  function languageLabel(language) {
178808
178757
  return language.charAt(0).toUpperCase() + language.slice(1);
178809
178758
  }
178759
+ function killRunningWorkers() {
178760
+ for (const [child, ownGroup] of liveWorkers) {
178761
+ killWorker(child, ownGroup);
178762
+ }
178763
+ }
178810
178764
 
178811
178765
  // src/scan/code/worker/runtime-probe.ts
178812
178766
  var PROBE_TIMEOUT_MS = 1e4;
178813
178767
  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({
178768
+ function probeRuntime(command, stripEnv, run = runScannerWorkerProcess) {
178769
+ const task = run({
178824
178770
  command: [...command, "--probe"],
178825
178771
  stdin: "",
178826
178772
  stripEnv,
@@ -178828,49 +178774,34 @@ function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcess
178828
178774
  maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
178829
178775
  });
178830
178776
  return {
178831
- promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
178832
- cancel: () => run.cancel()
178777
+ promise: task.promise.then(classifyProbe),
178778
+ cancel: () => task.cancel()
178833
178779
  };
178834
178780
  }
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
178781
  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` };
178782
+ if (result.ok) {
178783
+ if (result.exitCode !== 0) {
178784
+ const detail = firstLine(result.stderr);
178785
+ return {
178786
+ kind: "failed",
178787
+ reason: `probe exited with status ${result.exitCode}${detail === "" ? "" : `: ${detail}`}`
178788
+ };
178789
+ }
178790
+ return parseProbeOutput(result.stdout);
178866
178791
  }
178867
- if (code === "ENOBUFS") {
178868
- return {
178869
- kind: "failed",
178870
- reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178871
- };
178792
+ switch (result.cause?.type) {
178793
+ case "timeout":
178794
+ return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
178795
+ case "output-limit":
178796
+ return {
178797
+ kind: "failed",
178798
+ reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
178799
+ };
178800
+ case "signal":
178801
+ return { kind: "failed", reason: `probe terminated by signal ${result.cause.signal}` };
178802
+ default:
178803
+ return { kind: "unstartable", reason: reasonOf(result.error) };
178872
178804
  }
178873
- return { kind: "unstartable", reason: reasonOf(error) };
178874
178805
  }
178875
178806
  function parseProbeOutput(stdout) {
178876
178807
  const text = stdout.trim();
@@ -178880,7 +178811,7 @@ function parseProbeOutput(stdout) {
178880
178811
  } catch {
178881
178812
  return malformed(text);
178882
178813
  }
178883
- if (!isRecord2(value)) {
178814
+ if (!isRecord(value)) {
178884
178815
  return malformed(text);
178885
178816
  }
178886
178817
  if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
@@ -178898,40 +178829,9 @@ function malformed(text) {
178898
178829
  function firstLine(text) {
178899
178830
  return text.trim().split(/\r?\n/u)[0] ?? "";
178900
178831
  }
178901
- function isRecord2(value) {
178832
+ function isRecord(value) {
178902
178833
  return typeof value === "object" && value !== null && !Array.isArray(value);
178903
178834
  }
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
178835
 
178936
178836
  // src/scan/code/worker/scanner-executable.ts
178937
178837
  import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
@@ -179143,22 +179043,12 @@ function rememberProbe(key, generation, outcome) {
179143
179043
  function resolveRuntimeWorkerCommand(language, options) {
179144
179044
  const env = options.env ?? process.env;
179145
179045
  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
179046
  return cancelableSequence(async (run) => {
179157
179047
  const steps = resolutionSteps(language, options, env);
179158
179048
  let step = steps.next();
179159
179049
  while (step.done !== true) {
179160
179050
  const request = step.value;
179161
- step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
179051
+ step = steps.next(await run(() => cachedProbe(request, env, probe)));
179162
179052
  }
179163
179053
  return step.value;
179164
179054
  });
@@ -179266,24 +179156,13 @@ function isExecutableFile(path, platform) {
179266
179156
  }
179267
179157
  }
179268
179158
  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
179159
  const key = probeCacheKey(command, env);
179281
179160
  const cached = probeCache.get(key);
179282
179161
  if (cached !== undefined) {
179283
179162
  return settledCancelable(cached);
179284
179163
  }
179285
179164
  const generation = probeCacheGeneration;
179286
- return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
179165
+ return mapCancelable(probe(command, stripEnv), (outcome) => {
179287
179166
  rememberProbe(key, generation, outcome);
179288
179167
  return outcome;
179289
179168
  });
@@ -179339,16 +179218,12 @@ function failure(spec, language, code, reason) {
179339
179218
 
179340
179219
  // src/scan/code/dispatch.ts
179341
179220
  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));
179221
+ const resolve3 = (context) => typeof command === "function" ? settledCancelable(command(context)) : command.commandAsync(context);
179222
+ const prepare = (context) => mapCancelable(resolve3(context), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
179345
179223
  const scanBatchAsync = (files, options, context) => {
179346
179224
  if (files.length === 0) {
179347
179225
  return settledCancelable([]);
179348
179226
  }
179349
- if (commandAsync === undefined) {
179350
- return bound(context).scanFilesAsync(files, options, context);
179351
- }
179352
179227
  return cancelableSequence(async (step) => {
179353
179228
  const { adapter } = await step(() => prepare(context));
179354
179229
  return step(() => adapter.scanFilesAsync(files, options, context));
@@ -179356,8 +179231,6 @@ function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
179356
179231
  };
179357
179232
  return {
179358
179233
  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
179234
  scanFilesAsync: scanBatchAsync,
179362
179235
  prepare
179363
179236
  };
@@ -179367,20 +179240,6 @@ function boundWorkerAdapter(language, resolved, adapterOptions) {
179367
179240
  const runtime = resolved.ok ? resolved.runtime : [];
179368
179241
  const adapter = {
179369
179242
  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
179243
  scanFilesAsync(files, options, context) {
179385
179244
  if (files.length === 0) {
179386
179245
  return settledCancelable([]);
@@ -179388,36 +179247,28 @@ function boundWorkerAdapter(language, resolved, adapterOptions) {
179388
179247
  if (!resolved.ok) {
179389
179248
  return settledCancelable(failedBatch(language, files, resolved.diagnostic));
179390
179249
  }
179391
- const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
179250
+ const task = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
179392
179251
  return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
179393
179252
  },
179394
179253
  prepare: () => settledCancelable({ argv, runtime, adapter })
179395
179254
  };
179396
179255
  return { argv, runtime, adapter };
179397
179256
  }
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
179257
  function scanContext(projectRoot, options) {
179408
179258
  return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
179409
179259
  }
179410
- function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
179260
+ function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
179411
179261
  const plan = planCodeScan(files, codeInclude, read, options.onContent);
179412
179262
  const context = scanContext(projectRoot, options);
179413
179263
  return cancelableSequence(async (step) => {
179414
179264
  const cache = new Map;
179415
179265
  for (const batch of plan.batches) {
179416
179266
  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));
179267
+ const { argv, runtime, adapter } = "scanFilesAsync" in base ? await step(() => base.prepare(context)) : { argv: [], adapter: base };
179268
+ const keys = options.cache === undefined ? [] : batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
179269
+ const cached = options.cache === undefined ? batch.files.map(() => {
179270
+ return;
179271
+ }) : keys.map((key) => options.cache?.get(key));
179421
179272
  const missing = batch.files.filter((_, index) => cached[index] === undefined);
179422
179273
  const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
179423
179274
  let next = 0;
@@ -179425,7 +179276,7 @@ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {})
179425
179276
  fillBatch(plan, batch, results);
179426
179277
  results.forEach((scan, index) => {
179427
179278
  const key = keys[index];
179428
- if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
179279
+ if (options.cache !== undefined && scan !== undefined && key !== undefined && isReusableScan(scan)) {
179429
179280
  cache.set(key, scan);
179430
179281
  }
179431
179282
  });
@@ -179434,7 +179285,7 @@ function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {})
179434
179285
  });
179435
179286
  }
179436
179287
  function scanFilesAsync(adapter, files, options, context) {
179437
- return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
179288
+ return "scanFilesAsync" in adapter ? adapter.scanFilesAsync(files, options, context) : settledCancelable(adapter.scanFiles(files, options, context));
179438
179289
  }
179439
179290
  var builtInAdapters = {
179440
179291
  typescript: typeScriptAdapter,
@@ -179452,7 +179303,9 @@ function runtimeWorkerAdapter(language) {
179452
179303
  const command = scanners?.[language]?.command;
179453
179304
  return command === undefined ? { projectRoot } : { projectRoot, command };
179454
179305
  };
179455
- return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
179306
+ return createScannerWorkerAdapter(language, {
179307
+ commandAsync: (context) => resolveRuntimeWorkerCommand(language, resolution(context))
179308
+ });
179456
179309
  }
179457
179310
  function planCodeScan(files, codeInclude, read, onContent) {
179458
179311
  const slots = [];
@@ -179631,7 +179484,7 @@ function stripClosingHashes(text) {
179631
179484
  }
179632
179485
  function toAnchor(headingText) {
179633
179486
  const lowered = headingText.toLowerCase();
179634
- const replaced = lowered.replace(/[^\p{L}\p{N}]+/gu, "-");
179487
+ const replaced = lowered.replace(/[^\p{L}\p{N}_]+/gu, "-");
179635
179488
  return replaced.replace(/^-+/, "").replace(/-+$/, "");
179636
179489
  }
179637
179490
  function matchCodeComment(line, filePath, lineNumber) {
@@ -179773,37 +179626,28 @@ function flushDanglingWith(pending, diagnostics, message) {
179773
179626
  // src/query/project-scan.ts
179774
179627
  function scanProject(options) {
179775
179628
  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
179629
  if (!loaded.ok) {
179787
179630
  return cancelableSequence(async () => loaded);
179788
179631
  }
179789
179632
  const { inputs } = loaded;
179790
- const fingerprint = JSON.stringify(inputs.config);
179791
- const sameConfiguration = options.cache.fingerprint === fingerprint;
179792
- if (!sameConfiguration && options.cache.fingerprint !== "") {
179633
+ const previousCache = options.cache;
179634
+ const fingerprint = previousCache === undefined ? undefined : JSON.stringify(inputs.config);
179635
+ const sameConfiguration = previousCache !== undefined && previousCache.fingerprint === fingerprint;
179636
+ if (previousCache !== undefined && !sameConfiguration && previousCache.fingerprint !== "") {
179793
179637
  options.onConfigurationChange?.();
179794
179638
  }
179795
- const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179639
+ const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
179796
179640
  ...codeScanOptions(inputs, options),
179797
- ...sameConfiguration ? { cache: options.cache.entries } : {}
179641
+ ...previousCache === undefined ? {} : { cache: sameConfiguration ? previousCache.entries : new Map }
179798
179642
  });
179799
179643
  const docReads = readDocFiles(inputs, options);
179800
179644
  return cancelableSequence(async (step) => {
179801
179645
  const { cache, ...code } = await step(codeScan);
179802
- const scan = finishProjectScan(inputs, code, docReads, true);
179646
+ const scan = finishProjectScan(inputs, code, docReads, options.buildGraph);
179803
179647
  return {
179804
179648
  ok: true,
179805
179649
  scan,
179806
- cache: { fingerprint, entries: cache }
179650
+ cache: { fingerprint: fingerprint ?? "", entries: cache }
179807
179651
  };
179808
179652
  });
179809
179653
  }
@@ -179908,19 +179752,12 @@ class Project {
179908
179752
  this.overlay.delete(relPath);
179909
179753
  this.overlayRevision += 1;
179910
179754
  }
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
179755
  resolveAsync() {
179921
179756
  const revision = this.overlayRevision;
179922
- const scan = scanProjectAsync({
179757
+ const scan = scanProject({
179923
179758
  ...this.scanSources(new Map(this.overlay)),
179759
+ buildGraph: true,
179760
+ keepContent: true,
179924
179761
  cache: this.cache,
179925
179762
  onConfigurationChange: clearRuntimeProbeCache
179926
179763
  });
@@ -180419,12 +180256,12 @@ function fullChangeText(changes) {
180419
180256
  }
180420
180257
 
180421
180258
  // src/query/check.ts
180422
- function check(options) {
180259
+ async function check(options) {
180423
180260
  const audit = options.audit ?? false;
180424
- const outcome = scanProject({
180261
+ const outcome = await scanProject({
180425
180262
  projectRoot: options.projectRoot,
180426
180263
  ...options.adapters === undefined ? {} : { adapters: options.adapters }
180427
- });
180264
+ }).promise;
180428
180265
  if (!outcome.ok) {
180429
180266
  const sorted = sortDiagnostics(outcome.diagnostics);
180430
180267
  return { diagnostics: sorted, summary: summarizeDiagnostics(sorted) };
@@ -180498,8 +180335,8 @@ function collectGateViolations(result) {
180498
180335
  }
180499
180336
  return violations;
180500
180337
  }
180501
- function related(options) {
180502
- const outcome = scanProject({ projectRoot: options.projectRoot, buildGraph: true });
180338
+ async function related(options) {
180339
+ const outcome = await scanProject({ projectRoot: options.projectRoot, buildGraph: true }).promise;
180503
180340
  if (!outcome.ok) {
180504
180341
  return { ok: false, diagnostics: outcome.diagnostics };
180505
180342
  }
@@ -180557,12 +180394,12 @@ function computeContext(graph, contentByFile, inputFiles) {
180557
180394
  summary: { inputFiles: inputSet.size, contexts: contexts.length }
180558
180395
  };
180559
180396
  }
180560
- function context(options) {
180561
- const outcome = scanProject({
180397
+ async function context(options) {
180398
+ const outcome = await scanProject({
180562
180399
  projectRoot: options.projectRoot,
180563
180400
  buildGraph: true,
180564
180401
  keepContent: true
180565
- });
180402
+ }).promise;
180566
180403
  if (!outcome.ok) {
180567
180404
  return { ok: false, diagnostics: outcome.diagnostics };
180568
180405
  }
@@ -180636,12 +180473,12 @@ function compareBlocks(left, right) {
180636
180473
  }
180637
180474
 
180638
180475
  // src/query/graph-output.ts
180639
- function graph(options) {
180640
- const outcome = scanProject({
180476
+ async function graph(options) {
180477
+ const outcome = await scanProject({
180641
180478
  projectRoot: options.projectRoot,
180642
180479
  buildGraph: true,
180643
180480
  keepContent: true
180644
- });
180481
+ }).promise;
180645
180482
  if (!outcome.ok) {
180646
180483
  return { ok: false, diagnostics: outcome.diagnostics };
180647
180484
  }
@@ -181136,13 +180973,6 @@ class CliError extends Error {
181136
180973
  }
181137
180974
  }
181138
180975
 
181139
- class InitCliError extends CliError {
181140
- constructor(message, guidance) {
181141
- super(message, guidance);
181142
- this.name = "InitCliError";
181143
- }
181144
- }
181145
-
181146
180976
  class DiagnosticOutputError extends Error {
181147
180977
  constructor(message) {
181148
180978
  super(message);
@@ -182101,7 +181931,6 @@ function readFileOrUndefined(path) {
182101
181931
  }
182102
181932
 
182103
181933
  // src/setup/init-plan.ts
182104
- var CONFIG_FILE_NAME2 = "docbridge.config.json";
182105
181934
  var INIT_SKILL_NAMES = ["docbridge"];
182106
181935
  var LEGACY_SKILL_NAMES = [
182107
181936
  "docbridge-adopt",
@@ -182269,7 +182098,7 @@ function planConfigOperations(input) {
182269
182098
  return [
182270
182099
  {
182271
182100
  action,
182272
- path: CONFIG_FILE_NAME2,
182101
+ path: CONFIG_FILE_NAME,
182273
182102
  content,
182274
182103
  reason: "Create docbridge.config.json from discovered scope."
182275
182104
  }
@@ -182421,7 +182250,7 @@ function resolveInitWithAgentTarget(discovery, options) {
182421
182250
  return { target: discovery.agent.defaultTarget, error: undefined };
182422
182251
  }
182423
182252
  function readExistingConfig(projectRoot) {
182424
- const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
182253
+ const configPath = join12(projectRoot, CONFIG_FILE_NAME);
182425
182254
  let rawText;
182426
182255
  try {
182427
182256
  rawText = readFileSync7(configPath, "utf8");
@@ -182499,7 +182328,7 @@ function parseInitOptions(args, command) {
182499
182328
  if (arg === "--root") {
182500
182329
  const root = args[index + 1];
182501
182330
  if (root === undefined) {
182502
- throw new InitCliError("--root requires a path.", rootPathGuidance(command));
182331
+ throw new CliError("--root requires a path.", rootPathGuidance(command));
182503
182332
  }
182504
182333
  options.root = root;
182505
182334
  index += 1;
@@ -182508,22 +182337,22 @@ function parseInitOptions(args, command) {
182508
182337
  if (arg === "--agent-target") {
182509
182338
  const target = args[index + 1];
182510
182339
  if (target === undefined) {
182511
- throw new InitCliError("--agent-target requires a value.", agentTargetGuidance(command));
182340
+ throw new CliError("--agent-target requires a value.", agentTargetGuidance(command));
182512
182341
  }
182513
182342
  if (!AGENT_TARGETS.has(target)) {
182514
- throw new InitCliError(`Unknown agent target: ${target}. Supported values: codex, claude, both${command === "init" ? ", none" : ""}.`, agentTargetGuidance(command));
182343
+ throw new CliError(`Unknown agent target: ${target}. Supported values: codex, claude, both${command === "init" ? ", none" : ""}.`, agentTargetGuidance(command));
182515
182344
  }
182516
182345
  if (command === "init-with-agent" && target === "none") {
182517
- throw new InitCliError("init-with-agent requires an agent target other than none.", agentTargetGuidance(command));
182346
+ throw new CliError("init-with-agent requires an agent target other than none.", agentTargetGuidance(command));
182518
182347
  }
182519
182348
  options.agentTarget = target;
182520
182349
  index += 1;
182521
182350
  continue;
182522
182351
  }
182523
182352
  if (arg.startsWith("--")) {
182524
- throw new InitCliError(`Unknown option: ${arg}`, commandHelpGuidance(command));
182353
+ throw new CliError(`Unknown option: ${arg}`, commandHelpGuidance(command));
182525
182354
  }
182526
- throw new InitCliError(`Unexpected argument: ${arg}`, commandHelpGuidance(command));
182355
+ throw new CliError(`Unexpected argument: ${arg}`, commandHelpGuidance(command));
182527
182356
  }
182528
182357
  return options;
182529
182358
  }
@@ -182610,19 +182439,19 @@ function resolveConfirmedScope(input) {
182610
182439
  };
182611
182440
  }
182612
182441
  if (!input.prompts.isInteractive) {
182613
- throw new InitCliError("Interactive setup requires a TTY. Re-run with --yes for non-interactive mode.");
182442
+ throw new CliError("Interactive setup requires a TTY. Re-run with --yes for non-interactive mode.");
182614
182443
  }
182615
182444
  if (input.discovery.docs.ambiguous) {
182616
182445
  const choices = input.discovery.docs.candidates.map((candidate) => candidate.pattern);
182617
182446
  if (choices.length === 0) {
182618
- throw new InitCliError("Docs scope is ambiguous and no candidates were detected. Create docbridge.config.json manually.");
182447
+ throw new CliError("Docs scope is ambiguous and no candidates were detected. Create docbridge.config.json manually.");
182619
182448
  }
182620
182449
  const docsPattern2 = input.prompts.select("Choose docs scope:", choices, choices[0] ?? "");
182621
182450
  const languages2 = selectLanguages(input.discovery.code.languages, input.prompts);
182622
182451
  return { docsPattern: docsPattern2, languages: languages2 };
182623
182452
  }
182624
182453
  if (input.discovery.docs.recommended === undefined) {
182625
- throw new InitCliError("Docs scope could not be determined.");
182454
+ throw new CliError("Docs scope could not be determined.");
182626
182455
  }
182627
182456
  let docsPattern = input.discovery.docs.recommended.pattern;
182628
182457
  if (!input.prompts.confirm(`Use docs scope ${docsPattern}?`, true)) {
@@ -182656,7 +182485,7 @@ function selectLanguages(languages, prompts) {
182656
182485
  }
182657
182486
  }
182658
182487
  if (selected.length === 0) {
182659
- throw new InitCliError("At least one supported code language must remain in scope.");
182488
+ throw new CliError("At least one supported code language must remain in scope.");
182660
182489
  }
182661
182490
  return selected;
182662
182491
  }
@@ -182686,10 +182515,10 @@ function resolveProjectRoot(root, command) {
182686
182515
  try {
182687
182516
  stats = statSync4(projectRoot);
182688
182517
  } catch {
182689
- throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182518
+ throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
182690
182519
  }
182691
182520
  if (!stats.isDirectory()) {
182692
- throw new InitCliError(`Root path is not a directory: ${root}`, rootPathGuidance(command));
182521
+ throw new CliError(`Root path is not a directory: ${root}`, rootPathGuidance(command));
182693
182522
  }
182694
182523
  return projectRoot;
182695
182524
  }
@@ -182965,8 +182794,7 @@ function planUpgrade(input) {
182965
182794
  pending,
182966
182795
  messages,
182967
182796
  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
182797
+ requiresConfirmation: mode === "apply" && !input.options.dryRun && !input.options.yes && operations.some((operation) => operation.action === "overwrite" || operation.action === "remove")
182970
182798
  };
182971
182799
  }
182972
182800
  function planOperations(input) {
@@ -183149,7 +182977,7 @@ function runUpgrade(options, io, initRuntime, runtime) {
183149
182977
  });
183150
182978
  if (plan.mode === "check" || options.dryRun) {
183151
182979
  io.stdout(formatUpgradePlan(plan));
183152
- return plan.exitCode;
182980
+ return 0;
183153
182981
  }
183154
182982
  return applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime);
183155
182983
  }
@@ -183167,7 +182995,7 @@ function applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime) {
183167
182995
  applySkillOperation(projectRoot, packageRoot, operation);
183168
182996
  }
183169
182997
  io.stdout(formatUpgradePlan(plan));
183170
- return plan.exitCode;
182998
+ return 0;
183171
182999
  }
183172
183000
  function parseAgentTarget(value) {
183173
183001
  if (value === undefined) {
@@ -183192,6 +183020,31 @@ function resolveProjectRoot2(root) {
183192
183020
  return projectRoot;
183193
183021
  }
183194
183022
 
183023
+ // src/cli/worker-signals.ts
183024
+ async function killingWorkersOnSignal(scan) {
183025
+ const signals = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
183026
+ const handlers = new Map;
183027
+ const remove = () => {
183028
+ for (const [signal, handler] of handlers) {
183029
+ process.off(signal, handler);
183030
+ }
183031
+ };
183032
+ for (const signal of signals) {
183033
+ const handler = () => {
183034
+ killRunningWorkers();
183035
+ remove();
183036
+ process.kill(process.pid, signal);
183037
+ };
183038
+ handlers.set(signal, handler);
183039
+ process.on(signal, handler);
183040
+ }
183041
+ try {
183042
+ return await scan();
183043
+ } finally {
183044
+ remove();
183045
+ }
183046
+ }
183047
+
183195
183048
  // src/cli/index.ts
183196
183049
  var VERSION = package_default.version;
183197
183050
  function parseCheckOptions(args) {
@@ -183219,9 +183072,9 @@ function resolveProjectRoot3(root, command) {
183219
183072
  }
183220
183073
  return projectRoot;
183221
183074
  }
183222
- function runCheck(options, io) {
183075
+ async function runCheck(options, io) {
183223
183076
  const projectRoot = resolveProjectRoot3(options.root, "check");
183224
- const result = check({ projectRoot, audit: options.audit });
183077
+ const result = await killingWorkersOnSignal(() => check({ projectRoot, audit: options.audit }));
183225
183078
  if (options.json) {
183226
183079
  io.stdout(`${JSON.stringify(result, null, 2)}
183227
183080
  `);
@@ -183242,7 +183095,7 @@ See \`docbridge docs show troubleshooting\` for diagnostic codes and fixes.
183242
183095
  }
183243
183096
  return result.summary.errors > 0 ? 1 : 0;
183244
183097
  }
183245
- function runFileCommand(command, options, io, execute, render) {
183098
+ async function runFileCommand(command, options, io, execute, render) {
183246
183099
  const projectRoot = resolveProjectRoot3(options.root, command);
183247
183100
  const inputFiles = [...options.files];
183248
183101
  if (options.stdin) {
@@ -183250,7 +183103,7 @@ function runFileCommand(command, options, io, execute, render) {
183250
183103
  inputFiles.push(...readStdin().split(`
183251
183104
  `));
183252
183105
  }
183253
- const outcome = execute(projectRoot, inputFiles);
183106
+ const outcome = await killingWorkersOnSignal(async () => execute(projectRoot, inputFiles));
183254
183107
  if (!outcome.ok) {
183255
183108
  throw new DiagnosticOutputError(outcome.diagnostics.map(formatDiagnostic).join(`
183256
183109
  `));
@@ -183271,7 +183124,7 @@ function writeStructuredResult(result, options) {
183271
183124
  `);
183272
183125
  }
183273
183126
  }
183274
- function runRelated(options, io) {
183127
+ async function runRelated(options, io) {
183275
183128
  if (!options.stdin && options.files.length === 0) {
183276
183129
  throw new CliError("No input files were provided.", missingInputGuidance("related"));
183277
183130
  }
@@ -183300,7 +183153,7 @@ function runRelated(options, io) {
183300
183153
  return 0;
183301
183154
  });
183302
183155
  }
183303
- function runContext(options, io) {
183156
+ async function runContext(options, io) {
183304
183157
  if (!options.stdin && options.files.length === 0) {
183305
183158
  throw new CliError("No input files were provided.", missingInputGuidance("context"));
183306
183159
  }
@@ -183314,7 +183167,7 @@ function runContext(options, io) {
183314
183167
  return 0;
183315
183168
  });
183316
183169
  }
183317
- function runGraph(options, io) {
183170
+ async function runGraph(options, io) {
183318
183171
  return runFileCommand("graph", options, io, (projectRoot, inputFiles) => graph({ projectRoot, inputFiles, includeContent: options.includeContent }), (result, projectRoot, inputFiles) => {
183319
183172
  const normalizedInputFiles = normalizeChangedPaths(projectRoot, inputFiles);
183320
183173
  writeStructuredResult(result, {
@@ -183368,7 +183221,7 @@ var COMMAND_HANDLERS = {
183368
183221
  return 0;
183369
183222
  }
183370
183223
  };
183371
- function run(argv, io = {
183224
+ async function run(argv, io = {
183372
183225
  stdout: (text) => process.stdout.write(text),
183373
183226
  stderr: (text) => process.stderr.write(text)
183374
183227
  }, initRuntime = { prompts: createDefaultPrompts() }, cliRuntime = {}) {
@@ -183390,7 +183243,7 @@ function run(argv, io = {
183390
183243
  return 0;
183391
183244
  }
183392
183245
  if (isSubcommand(command)) {
183393
- const exitCode = COMMAND_HANDLERS[command](rest, io, initRuntime, cliRuntime);
183246
+ const exitCode = await COMMAND_HANDLERS[command](rest, io, initRuntime, cliRuntime);
183394
183247
  writeUpdateNotice(argv, io, initRuntime, cliRuntime);
183395
183248
  return exitCode;
183396
183249
  }
@@ -183455,7 +183308,7 @@ if (__require.main == __require.module) {
183455
183308
  const argv = process.argv.slice(2);
183456
183309
  const isTty = process.stderr.isTTY === true;
183457
183310
  const latest = await resolveLatestForInvocation(argv, process.env, isTty);
183458
- process.exitCode = run(argv, {
183311
+ process.exitCode = await run(argv, {
183459
183312
  stdout: (text) => process.stdout.write(text),
183460
183313
  stderr: (text) => process.stderr.write(text)
183461
183314
  }, { 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
 
@@ -255,8 +255,9 @@ Use the scanner-produced canonical symbol ID exactly.
255
255
  ## Heading anchors and reciprocity
256
256
 
257
257
  DocBridge creates anchors from ATX headings only. It lowercases the heading,
258
- collapses runs of whitespace and punctuation to `-`, preserves Unicode letters
259
- and numbers, and removes leading and trailing hyphens. `## Login Spec (v2)` is
258
+ collapses runs of whitespace and punctuation other than `_` to `-`, keeps `_`
259
+ as GitHub's heading slugs do, preserves Unicode letters and numbers, and removes
260
+ leading and trailing hyphens. `## Login Spec (v2)` is
260
261
  `#login-spec-v2`.
261
262
 
262
263
  Empty headings have no anchor; a `@code` annotation attached to an empty
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docbridge",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "A CLI that links Markdown specifications and code declarations. Runs on Node.js and Bun.",
5
5
  "keywords": [
6
6
  "bun",