docbridge 0.7.0 → 0.9.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/dist/index.js CHANGED
@@ -411,11 +411,11 @@ var require_codegen = __commonJS((exports) => {
411
411
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
412
412
  return `${varKind} ${this.name}${rhs};` + _n;
413
413
  }
414
- optimizeNames(names, constants) {
414
+ optimizeNames(names, constants2) {
415
415
  if (!names[this.name.str])
416
416
  return;
417
417
  if (this.rhs)
418
- this.rhs = optimizeExpr(this.rhs, names, constants);
418
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
419
419
  return this;
420
420
  }
421
421
  get names() {
@@ -433,10 +433,10 @@ var require_codegen = __commonJS((exports) => {
433
433
  render({ _n }) {
434
434
  return `${this.lhs} = ${this.rhs};` + _n;
435
435
  }
436
- optimizeNames(names, constants) {
436
+ optimizeNames(names, constants2) {
437
437
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
438
438
  return;
439
- this.rhs = optimizeExpr(this.rhs, names, constants);
439
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
440
440
  return this;
441
441
  }
442
442
  get names() {
@@ -502,8 +502,8 @@ var require_codegen = __commonJS((exports) => {
502
502
  optimizeNodes() {
503
503
  return `${this.code}` ? this : undefined;
504
504
  }
505
- optimizeNames(names, constants) {
506
- this.code = optimizeExpr(this.code, names, constants);
505
+ optimizeNames(names, constants2) {
506
+ this.code = optimizeExpr(this.code, names, constants2);
507
507
  return this;
508
508
  }
509
509
  get names() {
@@ -533,12 +533,12 @@ var require_codegen = __commonJS((exports) => {
533
533
  }
534
534
  return nodes.length > 0 ? this : undefined;
535
535
  }
536
- optimizeNames(names, constants) {
536
+ optimizeNames(names, constants2) {
537
537
  const { nodes } = this;
538
538
  let i = nodes.length;
539
539
  while (i--) {
540
540
  const n = nodes[i];
541
- if (n.optimizeNames(names, constants))
541
+ if (n.optimizeNames(names, constants2))
542
542
  continue;
543
543
  subtractNames(names, n.names);
544
544
  nodes.splice(i, 1);
@@ -595,12 +595,12 @@ var require_codegen = __commonJS((exports) => {
595
595
  return;
596
596
  return this;
597
597
  }
598
- optimizeNames(names, constants) {
598
+ optimizeNames(names, constants2) {
599
599
  var _a;
600
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants);
601
- if (!(super.optimizeNames(names, constants) || this.else))
600
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants2);
601
+ if (!(super.optimizeNames(names, constants2) || this.else))
602
602
  return;
603
- this.condition = optimizeExpr(this.condition, names, constants);
603
+ this.condition = optimizeExpr(this.condition, names, constants2);
604
604
  return this;
605
605
  }
606
606
  get names() {
@@ -625,10 +625,10 @@ var require_codegen = __commonJS((exports) => {
625
625
  render(opts) {
626
626
  return `for(${this.iteration})` + super.render(opts);
627
627
  }
628
- optimizeNames(names, constants) {
629
- if (!super.optimizeNames(names, constants))
628
+ optimizeNames(names, constants2) {
629
+ if (!super.optimizeNames(names, constants2))
630
630
  return;
631
- this.iteration = optimizeExpr(this.iteration, names, constants);
631
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
632
632
  return this;
633
633
  }
634
634
  get names() {
@@ -666,10 +666,10 @@ var require_codegen = __commonJS((exports) => {
666
666
  render(opts) {
667
667
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
668
668
  }
669
- optimizeNames(names, constants) {
670
- if (!super.optimizeNames(names, constants))
669
+ optimizeNames(names, constants2) {
670
+ if (!super.optimizeNames(names, constants2))
671
671
  return;
672
- this.iterable = optimizeExpr(this.iterable, names, constants);
672
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
673
673
  return this;
674
674
  }
675
675
  get names() {
@@ -714,11 +714,11 @@ var require_codegen = __commonJS((exports) => {
714
714
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
715
715
  return this;
716
716
  }
717
- optimizeNames(names, constants) {
717
+ optimizeNames(names, constants2) {
718
718
  var _a, _b;
719
- super.optimizeNames(names, constants);
720
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants);
721
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
719
+ super.optimizeNames(names, constants2);
720
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants2);
721
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
722
722
  return this;
723
723
  }
724
724
  get names() {
@@ -992,7 +992,7 @@ var require_codegen = __commonJS((exports) => {
992
992
  function addExprNames(names, from) {
993
993
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
994
994
  }
995
- function optimizeExpr(expr, names, constants) {
995
+ function optimizeExpr(expr, names, constants2) {
996
996
  if (expr instanceof code_1.Name)
997
997
  return replaceName(expr);
998
998
  if (!canOptimize(expr))
@@ -1007,14 +1007,14 @@ var require_codegen = __commonJS((exports) => {
1007
1007
  return items;
1008
1008
  }, []));
1009
1009
  function replaceName(n) {
1010
- const c = constants[n.str];
1010
+ const c = constants2[n.str];
1011
1011
  if (c === undefined || names[n.str] !== 1)
1012
1012
  return n;
1013
1013
  delete names[n.str];
1014
1014
  return c;
1015
1015
  }
1016
1016
  function canOptimize(e) {
1017
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
1017
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
1018
1018
  }
1019
1019
  }
1020
1020
  function subtractNames(names, from) {
@@ -2919,7 +2919,7 @@ var require_compile = __commonJS((exports) => {
2919
2919
  const schOrFunc = root.refs[ref];
2920
2920
  if (schOrFunc)
2921
2921
  return schOrFunc;
2922
- let _sch = resolve.call(this, root, ref);
2922
+ let _sch = resolve2.call(this, root, ref);
2923
2923
  if (_sch === undefined) {
2924
2924
  const schema = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
2925
2925
  const { schemaId } = this.opts;
@@ -2946,7 +2946,7 @@ var require_compile = __commonJS((exports) => {
2946
2946
  function sameSchemaEnv(s1, s2) {
2947
2947
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
2948
2948
  }
2949
- function resolve(root, ref) {
2949
+ function resolve2(root, ref) {
2950
2950
  let sch;
2951
2951
  while (typeof (sch = this.refs[ref]) == "string")
2952
2952
  ref = sch;
@@ -3532,7 +3532,7 @@ var require_fast_uri = __commonJS((exports, module) => {
3532
3532
  }
3533
3533
  return uri;
3534
3534
  }
3535
- function resolve(baseURI, relativeURI, options) {
3535
+ function resolve2(baseURI, relativeURI, options) {
3536
3536
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3537
3537
  const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3538
3538
  const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
@@ -3817,7 +3817,7 @@ var require_fast_uri = __commonJS((exports, module) => {
3817
3817
  var fastUri = {
3818
3818
  SCHEMES,
3819
3819
  normalize,
3820
- resolve,
3820
+ resolve: resolve2,
3821
3821
  resolveComponent,
3822
3822
  equal,
3823
3823
  serialize,
@@ -7155,7 +7155,7 @@ var require_typescript = __commonJS((exports, module) => {
7155
7155
  compact: () => compact,
7156
7156
  compareBooleans: () => compareBooleans,
7157
7157
  compareDataObjects: () => compareDataObjects,
7158
- compareDiagnostics: () => compareDiagnostics,
7158
+ compareDiagnostics: () => compareDiagnostics2,
7159
7159
  compareEmitHelpers: () => compareEmitHelpers,
7160
7160
  compareNumberOfDirectorySeparators: () => compareNumberOfDirectorySeparators,
7161
7161
  comparePaths: () => comparePaths2,
@@ -7474,7 +7474,7 @@ var require_typescript = __commonJS((exports, module) => {
7474
7474
  forEachUnique: () => forEachUnique,
7475
7475
  forEachYieldExpression: () => forEachYieldExpression,
7476
7476
  formatColorAndReset: () => formatColorAndReset,
7477
- formatDiagnostic: () => formatDiagnostic,
7477
+ formatDiagnostic: () => formatDiagnostic2,
7478
7478
  formatDiagnostics: () => formatDiagnostics,
7479
7479
  formatDiagnosticsWithColorAndContext: () => formatDiagnosticsWithColorAndContext,
7480
7480
  formatGeneratedName: () => formatGeneratedName,
@@ -14966,7 +14966,7 @@ ${lanes.join(`
14966
14966
  return process.memoryUsage().heapUsed;
14967
14967
  },
14968
14968
  getFileSize(path) {
14969
- const stat = statSync(path);
14969
+ const stat = statSync2(path);
14970
14970
  if (stat == null ? undefined : stat.isFile()) {
14971
14971
  return stat.size;
14972
14972
  }
@@ -15009,7 +15009,7 @@ ${lanes.join(`
15009
15009
  }
15010
15010
  };
15011
15011
  return nodeSystem;
15012
- function statSync(path) {
15012
+ function statSync2(path) {
15013
15013
  try {
15014
15014
  return _fs.statSync(path, statSyncOptions);
15015
15015
  } catch {
@@ -15061,7 +15061,7 @@ ${lanes.join(`
15061
15061
  activeSession.post("Profiler.stop", (err, { profile }) => {
15062
15062
  var _a;
15063
15063
  if (!err) {
15064
- if ((_a = statSync(profilePath)) == null ? undefined : _a.isDirectory()) {
15064
+ if ((_a = statSync2(profilePath)) == null ? undefined : _a.isDirectory()) {
15065
15065
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
15066
15066
  }
15067
15067
  try {
@@ -15170,7 +15170,7 @@ ${lanes.join(`
15170
15170
  let stat;
15171
15171
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
15172
15172
  const name = combinePaths(path, entry);
15173
- stat = statSync(name);
15173
+ stat = statSync2(name);
15174
15174
  if (!stat) {
15175
15175
  continue;
15176
15176
  }
@@ -15194,7 +15194,7 @@ ${lanes.join(`
15194
15194
  return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
15195
15195
  }
15196
15196
  function fileSystemEntryExists(path, entryKind) {
15197
- const stat = statSync(path);
15197
+ const stat = statSync2(path);
15198
15198
  if (!stat) {
15199
15199
  return false;
15200
15200
  }
@@ -15228,7 +15228,7 @@ ${lanes.join(`
15228
15228
  }
15229
15229
  function getModifiedTime3(path) {
15230
15230
  var _a;
15231
- return (_a = statSync(path)) == null ? undefined : _a.mtime;
15231
+ return (_a = statSync2(path)) == null ? undefined : _a.mtime;
15232
15232
  }
15233
15233
  function setModifiedTime(path, time) {
15234
15234
  try {
@@ -20956,7 +20956,7 @@ ${lanes.join(`
20956
20956
  return pathIsRelative(moduleName) || isRootedDiskPath(moduleName);
20957
20957
  }
20958
20958
  function sortAndDeduplicateDiagnostics(diagnostics) {
20959
- return sortAndDeduplicate(diagnostics, compareDiagnostics, diagnosticsEqualityComparer);
20959
+ return sortAndDeduplicate(diagnostics, compareDiagnostics2, diagnosticsEqualityComparer);
20960
20960
  }
20961
20961
  var targetToLibMap = /* @__PURE__ */ new Map([
20962
20962
  [99, "lib.esnext.full.d.ts"],
@@ -27716,7 +27716,7 @@ ${lanes.join(`
27716
27716
  function getDiagnosticFilePath(diagnostic) {
27717
27717
  return diagnostic.file ? diagnostic.file.path : undefined;
27718
27718
  }
27719
- function compareDiagnostics(d1, d2) {
27719
+ function compareDiagnostics2(d1, d2) {
27720
27720
  return compareDiagnosticsSkipRelatedInformation(d1, d2) || compareRelatedInformation(d1, d2) || 0;
27721
27721
  }
27722
27722
  function compareDiagnosticsSkipRelatedInformation(d1, d2) {
@@ -27731,7 +27731,7 @@ ${lanes.join(`
27731
27731
  if (d1.relatedInformation && d2.relatedInformation) {
27732
27732
  return compareValues(d2.relatedInformation.length, d1.relatedInformation.length) || forEach(d1.relatedInformation, (d1i, index) => {
27733
27733
  const d2i = d2.relatedInformation[index];
27734
- return compareDiagnostics(d1i, d2i);
27734
+ return compareDiagnostics2(d1i, d2i);
27735
27735
  }) || 0;
27736
27736
  }
27737
27737
  return d1.relatedInformation ? -1 : 1;
@@ -54786,7 +54786,7 @@ ${lanes.join(`
54786
54786
  err.relatedInformation = err.relatedInformation || [];
54787
54787
  const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName2);
54788
54788
  const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here);
54789
- if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, (r) => compareDiagnostics(r, followOnMessage) === 0 || compareDiagnostics(r, leadingMessage) === 0))
54789
+ if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, (r) => compareDiagnostics2(r, followOnMessage) === 0 || compareDiagnostics2(r, leadingMessage) === 0))
54790
54790
  continue;
54791
54791
  addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage);
54792
54792
  }
@@ -87995,7 +87995,7 @@ ${lanes.join(`
87995
87995
  }
87996
87996
  const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
87997
87997
  if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
87998
- const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics);
87998
+ const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics2);
87999
87999
  return concatenate(deferredGlobalDiagnostics, semanticDiagnostics);
88000
88000
  } else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) {
88001
88001
  return concatenate(currentGlobalDiagnostics, semanticDiagnostics);
@@ -105133,14 +105133,14 @@ ${lanes.join(`
105133
105133
  }
105134
105134
  }
105135
105135
  function createImportCallExpressionAMD(arg, containsLexicalThis) {
105136
- const resolve = factory2.createUniqueName("resolve");
105136
+ const resolve2 = factory2.createUniqueName("resolve");
105137
105137
  const reject = factory2.createUniqueName("reject");
105138
105138
  const parameters = [
105139
- factory2.createParameterDeclaration(undefined, undefined, resolve),
105139
+ factory2.createParameterDeclaration(undefined, undefined, resolve2),
105140
105140
  factory2.createParameterDeclaration(undefined, undefined, reject)
105141
105141
  ];
105142
105142
  const body = factory2.createBlock([
105143
- factory2.createExpressionStatement(factory2.createCallExpression(factory2.createIdentifier("require"), undefined, [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve, reject]))
105143
+ factory2.createExpressionStatement(factory2.createCallExpression(factory2.createIdentifier("require"), undefined, [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve2, reject]))
105144
105144
  ]);
105145
105145
  let func;
105146
105146
  if (languageVersion >= 2) {
@@ -113910,11 +113910,11 @@ ${lanes.join(`
113910
113910
  function formatDiagnostics(diagnostics, host) {
113911
113911
  let output = "";
113912
113912
  for (const diagnostic of diagnostics) {
113913
- output += formatDiagnostic(diagnostic, host);
113913
+ output += formatDiagnostic2(diagnostic, host);
113914
113914
  }
113915
113915
  return output;
113916
113916
  }
113917
- function formatDiagnostic(diagnostic, host) {
113917
+ function formatDiagnostic2(diagnostic, host) {
113918
113918
  const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
113919
113919
  if (diagnostic.file) {
113920
113920
  const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
@@ -119897,7 +119897,7 @@ ${lanes.join(`
119897
119897
  getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames)
119898
119898
  };
119899
119899
  if (!pretty) {
119900
- return (diagnostic) => system.write(formatDiagnostic(diagnostic, host));
119900
+ return (diagnostic) => system.write(formatDiagnostic2(diagnostic, host));
119901
119901
  }
119902
119902
  const diagnostics = new Array(1);
119903
119903
  return (diagnostic) => {
@@ -163155,7 +163155,7 @@ ${options.prefix}` : `
163155
163155
  compact: () => compact,
163156
163156
  compareBooleans: () => compareBooleans,
163157
163157
  compareDataObjects: () => compareDataObjects,
163158
- compareDiagnostics: () => compareDiagnostics,
163158
+ compareDiagnostics: () => compareDiagnostics2,
163159
163159
  compareEmitHelpers: () => compareEmitHelpers,
163160
163160
  compareNumberOfDirectorySeparators: () => compareNumberOfDirectorySeparators,
163161
163161
  comparePaths: () => comparePaths2,
@@ -163474,7 +163474,7 @@ ${options.prefix}` : `
163474
163474
  forEachUnique: () => forEachUnique,
163475
163475
  forEachYieldExpression: () => forEachYieldExpression,
163476
163476
  formatColorAndReset: () => formatColorAndReset,
163477
- formatDiagnostic: () => formatDiagnostic,
163477
+ formatDiagnostic: () => formatDiagnostic2,
163478
163478
  formatDiagnostics: () => formatDiagnostics,
163479
163479
  formatDiagnosticsWithColorAndContext: () => formatDiagnosticsWithColorAndContext,
163480
163480
  formatGeneratedName: () => formatGeneratedName,
@@ -175315,8 +175315,8 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
175315
175315
  installPackage(options) {
175316
175316
  this.packageInstallId++;
175317
175317
  const request = { kind: "installPackage", ...options, id: this.packageInstallId };
175318
- const promise = new Promise((resolve, reject) => {
175319
- (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map)).set(this.packageInstallId, { resolve, reject });
175318
+ const promise = new Promise((resolve2, reject) => {
175319
+ (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map)).set(this.packageInstallId, { resolve: resolve2, reject });
175320
175320
  });
175321
175321
  this.installer.send(request);
175322
175322
  return promise;
@@ -175576,12 +175576,12 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
175576
175576
  });
175577
175577
 
175578
175578
  // src/cli/index.ts
175579
- import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
175580
- import { join as join9, resolve as resolve4 } from "node:path";
175579
+ import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
175580
+ import { join as join14, resolve as resolve5 } from "node:path";
175581
175581
  // package.json
175582
175582
  var package_default = {
175583
175583
  name: "docbridge",
175584
- version: "0.7.0",
175584
+ version: "0.9.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",
@@ -175624,6 +175624,7 @@ var package_default = {
175624
175624
  devDependencies: {
175625
175625
  "@types/bun": "^1.3.14",
175626
175626
  ajv: "^8.20.0",
175627
+ "markdown-it": "15.0.1",
175627
175628
  oxfmt: "0.57.0",
175628
175629
  oxlint: "1.73.0",
175629
175630
  typescript: "^5.9.0"
@@ -175636,20 +175637,7 @@ var package_default = {
175636
175637
 
175637
175638
  // src/core/config.ts
175638
175639
  import { readFileSync as readFileSync2 } from "node:fs";
175639
- import { join as join4 } from "node:path";
175640
-
175641
- // src/core/code-language.ts
175642
- import { accessSync, chmodSync, constants, existsSync, realpathSync, statSync } from "node:fs";
175643
- import { dirname as dirname2, join as join3, resolve } from "node:path";
175644
- import { fileURLToPath } from "node:url";
175645
-
175646
- // src/core/code-adapter-registry.ts
175647
- var codeAdapters = {};
175648
-
175649
- // src/core/error.ts
175650
- function reasonOf(error) {
175651
- return error instanceof Error ? error.message : String(error);
175652
- }
175640
+ import { join as join2 } from "node:path";
175653
175641
 
175654
175642
  // src/core/glob.ts
175655
175643
  import { lstatSync, readFileSync, readdirSync } from "node:fs";
@@ -175802,12 +175790,562 @@ function escapeRegExpChar(char) {
175802
175790
  return /[.*+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
175803
175791
  }
175804
175792
 
175793
+ // src/core/code-language.ts
175794
+ var KNOWN_CODE_LANGUAGES = [
175795
+ "typescript",
175796
+ "swift",
175797
+ "dart",
175798
+ "rust"
175799
+ ];
175800
+ function isCodeLanguage(value) {
175801
+ return KNOWN_CODE_LANGUAGES.includes(value);
175802
+ }
175803
+ function collectCodeFiles(projectRoot, codeInclude) {
175804
+ const collected = [];
175805
+ const seen = new Set;
175806
+ for (const language of KNOWN_CODE_LANGUAGES) {
175807
+ const entry = codeInclude[language];
175808
+ if (entry === undefined) {
175809
+ continue;
175810
+ }
175811
+ for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175812
+ if (seen.has(relPath)) {
175813
+ continue;
175814
+ }
175815
+ seen.add(relPath);
175816
+ collected.push({ language, relPath });
175817
+ }
175818
+ }
175819
+ collected.sort((left, right) => comparePaths(left.relPath, right.relPath));
175820
+ return collected;
175821
+ }
175822
+ function codeFileOwners(projectRoot, codeInclude) {
175823
+ const owners = new Map;
175824
+ for (const language of KNOWN_CODE_LANGUAGES) {
175825
+ const entry = codeInclude[language];
175826
+ if (entry === undefined) {
175827
+ continue;
175828
+ }
175829
+ for (const relPath of collectFiles(projectRoot, entry.patterns)) {
175830
+ const existing = owners.get(relPath);
175831
+ if (existing === undefined) {
175832
+ owners.set(relPath, [language]);
175833
+ } else if (!existing.includes(language)) {
175834
+ existing.push(language);
175835
+ }
175836
+ }
175837
+ }
175838
+ return owners;
175839
+ }
175840
+
175841
+ // src/core/config.ts
175842
+ var CONFIG_FILE_NAME = "docbridge.config.json";
175843
+ var EMPTY_CONFIG = {
175844
+ include: { code: {}, docs: [] }
175845
+ };
175846
+ var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
175847
+ var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
175848
+ var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
175849
+ var LANGUAGE_SUFFIX = {
175850
+ typescript: ".ts",
175851
+ swift: ".swift",
175852
+ dart: ".dart",
175853
+ rust: ".rs"
175854
+ };
175855
+ var LANGUAGE_VISIBILITY = {
175856
+ typescript: ["public", "protected", "private"],
175857
+ swift: ["public", "open", "internal"],
175858
+ dart: ["public"],
175859
+ rust: ["pub", "private"]
175860
+ };
175861
+ function loadConfig(projectRoot) {
175862
+ let rawText;
175863
+ try {
175864
+ rawText = readFileSync2(join2(projectRoot, CONFIG_FILE_NAME), "utf8");
175865
+ } catch {
175866
+ rawText = undefined;
175867
+ }
175868
+ const resolved = resolveConfig(rawText);
175869
+ if (!resolved.ok) {
175870
+ return resolved;
175871
+ }
175872
+ const overlapDiagnostics = detectLanguageOverlap(projectRoot, resolved.config.include.code);
175873
+ if (overlapDiagnostics.length === 0) {
175874
+ return resolved;
175875
+ }
175876
+ return {
175877
+ config: EMPTY_CONFIG,
175878
+ diagnostics: [...resolved.diagnostics, ...overlapDiagnostics],
175879
+ ok: false
175880
+ };
175881
+ }
175882
+ function resolveConfig(rawText) {
175883
+ if (rawText === undefined) {
175884
+ return {
175885
+ config: EMPTY_CONFIG,
175886
+ ok: false,
175887
+ diagnostics: [
175888
+ configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
175889
+ ]
175890
+ };
175891
+ }
175892
+ let parsed;
175893
+ try {
175894
+ parsed = JSON.parse(rawText);
175895
+ } catch (error) {
175896
+ const reason = error instanceof Error ? error.message : String(error);
175897
+ return {
175898
+ config: EMPTY_CONFIG,
175899
+ ok: false,
175900
+ diagnostics: [
175901
+ configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
175902
+ ]
175903
+ };
175904
+ }
175905
+ const diagnostics = [];
175906
+ if (!isPlainObject(parsed)) {
175907
+ diagnostics.push(configDiagnostic("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
175908
+ return { config: EMPTY_CONFIG, diagnostics, ok: false };
175909
+ }
175910
+ for (const key of Object.keys(parsed)) {
175911
+ if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
175912
+ diagnostics.push(configDiagnostic("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
175913
+ }
175914
+ }
175915
+ if ("$schema" in parsed && typeof parsed.$schema !== "string") {
175916
+ diagnostics.push(configDiagnostic("config_invalid_value", "$schema", "`$schema` must be a string."));
175917
+ }
175918
+ const include = parsed.include;
175919
+ if (!isPlainObject(include)) {
175920
+ diagnostics.push(configDiagnostic("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
175921
+ return { config: EMPTY_CONFIG, diagnostics, ok: false };
175922
+ }
175923
+ for (const key of Object.keys(include)) {
175924
+ if (!KNOWN_INCLUDE_KEYS.has(key)) {
175925
+ diagnostics.push(configDiagnostic("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
175926
+ }
175927
+ }
175928
+ const code = validateCodeInclude(include.code, diagnostics);
175929
+ validatePatternArray(include.docs, "docs", ".md", false, diagnostics);
175930
+ const ok = diagnostics.length === 0;
175931
+ const config = ok ? { include: { code, docs: include.docs } } : EMPTY_CONFIG;
175932
+ return { config, diagnostics, ok };
175933
+ }
175934
+ function validateCodeInclude(value, diagnostics) {
175935
+ if (Array.isArray(value)) {
175936
+ diagnostics.push(configDiagnostic("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
175937
+ return {};
175938
+ }
175939
+ if (!isPlainObject(value)) {
175940
+ diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
175941
+ return {};
175942
+ }
175943
+ const keys = Object.keys(value);
175944
+ if (keys.length === 0) {
175945
+ diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
175946
+ return {};
175947
+ }
175948
+ const result = {};
175949
+ for (const key of keys) {
175950
+ if (!isCodeLanguage(key)) {
175951
+ diagnostics.push(configDiagnostic("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
175952
+ continue;
175953
+ }
175954
+ const entry = validateCodeEntry(key, value[key], diagnostics);
175955
+ if (entry !== undefined) {
175956
+ result[key] = entry;
175957
+ }
175958
+ }
175959
+ return result;
175960
+ }
175961
+ function validateCodeEntry(language, value, diagnostics) {
175962
+ const target = `include.code.${language}`;
175963
+ if (!isPlainObject(value)) {
175964
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
175965
+ return;
175966
+ }
175967
+ for (const key of Object.keys(value)) {
175968
+ if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
175969
+ diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
175970
+ }
175971
+ }
175972
+ const before = diagnostics.length;
175973
+ validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIX[language], language === "typescript", diagnostics);
175974
+ if ("visibility" in value) {
175975
+ if (!isStringArray(value.visibility)) {
175976
+ diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
175977
+ } else {
175978
+ validateVisibilityOptions(language, value.visibility, target, diagnostics);
175979
+ }
175980
+ }
175981
+ if (diagnostics.length !== before) {
175982
+ return;
175983
+ }
175984
+ const entry = { patterns: value.patterns };
175985
+ if (isStringArray(value.visibility)) {
175986
+ entry.visibility = value.visibility;
175987
+ }
175988
+ return entry;
175989
+ }
175990
+ function validateVisibilityOptions(language, values, target, diagnostics) {
175991
+ const allowed = LANGUAGE_VISIBILITY[language];
175992
+ if (allowed.length === 0) {
175993
+ diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
175994
+ return;
175995
+ }
175996
+ for (const value of values) {
175997
+ if (!allowed.includes(value)) {
175998
+ diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
175999
+ }
176000
+ }
176001
+ }
176002
+ function validatePatternArray(value, field, requiredSuffix, excludeDeclarationFiles, diagnostics) {
176003
+ const target = `include.${field}`;
176004
+ if (!Array.isArray(value)) {
176005
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
176006
+ return;
176007
+ }
176008
+ if (value.length === 0) {
176009
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
176010
+ return;
176011
+ }
176012
+ for (const pattern of value) {
176013
+ if (typeof pattern !== "string") {
176014
+ diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
176015
+ continue;
176016
+ }
176017
+ const globResult = validateGlobPattern(pattern);
176018
+ if (!globResult.ok) {
176019
+ diagnostics.push(configDiagnostic("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
176020
+ continue;
176021
+ }
176022
+ const suffixError = checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles);
176023
+ if (suffixError !== undefined) {
176024
+ diagnostics.push(configDiagnostic("config_invalid_value", pattern, suffixError));
176025
+ }
176026
+ }
176027
+ }
176028
+ function checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles) {
176029
+ if (!pattern.endsWith(requiredSuffix)) {
176030
+ return `Pattern must end with \`${requiredSuffix}\`.`;
176031
+ }
176032
+ if (excludeDeclarationFiles && pattern.endsWith(".d.ts")) {
176033
+ return "Pattern must not target `.d.ts` declaration files.";
176034
+ }
176035
+ return;
176036
+ }
176037
+ function detectLanguageOverlap(projectRoot, code) {
176038
+ const diagnostics = [];
176039
+ for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
176040
+ if (owners.length > 1) {
176041
+ diagnostics.push(configDiagnostic("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
176042
+ }
176043
+ }
176044
+ return diagnostics;
176045
+ }
176046
+ function configDiagnostic(code, target, message) {
176047
+ return { severity: "error", code, target, message };
176048
+ }
176049
+ function isPlainObject(value) {
176050
+ return typeof value === "object" && value !== null && !Array.isArray(value);
176051
+ }
176052
+ function isStringArray(value) {
176053
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
176054
+ }
176055
+
176056
+ // src/core/diagnostics.ts
176057
+ function sortDiagnostics(diagnostics) {
176058
+ return [...diagnostics].toSorted(compareDiagnostics);
176059
+ }
176060
+ function summarizeDiagnostics(diagnostics) {
176061
+ const summary = { errors: 0, warnings: 0 };
176062
+ for (const diagnostic of diagnostics) {
176063
+ if (diagnostic.severity === "error") {
176064
+ summary.errors += 1;
176065
+ } else {
176066
+ summary.warnings += 1;
176067
+ }
176068
+ }
176069
+ return summary;
176070
+ }
176071
+ function formatDiagnostic(diagnostic) {
176072
+ const location = diagnostic.location;
176073
+ if (location === undefined) {
176074
+ return `${diagnostic.target} ${diagnostic.severity} ${diagnostic.code} - ${diagnostic.message}`;
176075
+ }
176076
+ const prefix = `${location.filePath}:${location.line}:${location.column}`;
176077
+ return `${prefix} ${diagnostic.severity} ${diagnostic.code} ${diagnostic.target} - ${diagnostic.message}`;
176078
+ }
176079
+ function formatSummary(summary) {
176080
+ return `Summary: ${summary.errors} ${pluralize("error", summary.errors)}, ${summary.warnings} ${pluralize("warning", summary.warnings)}`;
176081
+ }
176082
+ function collectErroredFiles(diagnostics) {
176083
+ const errored = new Set;
176084
+ for (const diagnostic of diagnostics) {
176085
+ if (diagnostic.code === "file_read_error" || diagnostic.code === "code_parse_error" || isFileScopedScannerDiagnostic(diagnostic)) {
176086
+ errored.add(diagnostic.target);
176087
+ }
176088
+ }
176089
+ return errored;
176090
+ }
176091
+ function isFileScopedScannerDiagnostic(diagnostic) {
176092
+ return (diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed") && diagnostic.language !== undefined && diagnostic.target !== diagnostic.language;
176093
+ }
176094
+ function compareDiagnostics(left, right) {
176095
+ const leftLocation = left.location;
176096
+ const rightLocation = right.location;
176097
+ if (leftLocation === undefined && rightLocation !== undefined) {
176098
+ return -1;
176099
+ }
176100
+ if (leftLocation !== undefined && rightLocation === undefined) {
176101
+ return 1;
176102
+ }
176103
+ if (leftLocation !== undefined && rightLocation !== undefined) {
176104
+ return compareString(leftLocation.filePath, rightLocation.filePath) || compareNumber(leftLocation.line, rightLocation.line) || compareNumber(leftLocation.column, rightLocation.column) || compareString(left.code, right.code) || compareString(left.target, right.target);
176105
+ }
176106
+ return compareString(left.code, right.code) || compareString(left.target, right.target);
176107
+ }
176108
+ function compareString(left, right) {
176109
+ return left.localeCompare(right);
176110
+ }
176111
+ function compareNumber(left, right) {
176112
+ return left - right;
176113
+ }
176114
+ function pluralize(word, count) {
176115
+ return count === 1 ? word : `${word}s`;
176116
+ }
176117
+
176118
+ // src/core/endpoint.ts
176119
+ function filePathOf(endpoint) {
176120
+ const hashIndex = endpoint.indexOf("#");
176121
+ return hashIndex === -1 ? endpoint : endpoint.slice(0, hashIndex);
176122
+ }
176123
+ function fragmentOf(endpoint) {
176124
+ const hashIndex = endpoint.indexOf("#");
176125
+ return hashIndex === -1 ? endpoint : endpoint.slice(hashIndex + 1);
176126
+ }
176127
+ function compareEndpointOrder(left, right) {
176128
+ return left.filePath.localeCompare(right.filePath) || left.line - right.line || left.column - right.column || left.endpoint.localeCompare(right.endpoint);
176129
+ }
176130
+ function endpointRange(element) {
176131
+ const range = element.kind === "code" ? element.nameRange : element.headingTextRange;
176132
+ if (range !== undefined) {
176133
+ return range;
176134
+ }
176135
+ const { line, column } = element.location;
176136
+ return { start: { line, column }, end: { line, column } };
176137
+ }
176138
+
176139
+ // src/core/graph.ts
176140
+ function buildLinkGraph(codeFiles, docFiles) {
176141
+ const codeByEndpoint = new Map;
176142
+ for (const file of codeFiles) {
176143
+ for (const symbol of file.symbols) {
176144
+ if (!codeByEndpoint.has(symbol.endpoint)) {
176145
+ codeByEndpoint.set(symbol.endpoint, symbol);
176146
+ }
176147
+ }
176148
+ }
176149
+ const docByEndpoint = new Map;
176150
+ for (const file of docFiles) {
176151
+ for (const anchor of file.anchors) {
176152
+ if (!docByEndpoint.has(anchor.endpoint)) {
176153
+ docByEndpoint.set(anchor.endpoint, anchor);
176154
+ }
176155
+ }
176156
+ }
176157
+ const counterparts = new Map;
176158
+ const addEdge = (a, b) => {
176159
+ addTo(counterparts, a, b);
176160
+ addTo(counterparts, b, a);
176161
+ };
176162
+ for (const file of codeFiles) {
176163
+ for (const link of file.links) {
176164
+ if (codeByEndpoint.has(link.source) && docByEndpoint.has(link.target)) {
176165
+ addEdge(link.source, link.target);
176166
+ }
176167
+ }
176168
+ }
176169
+ for (const file of docFiles) {
176170
+ for (const link of file.links) {
176171
+ if (docByEndpoint.has(link.source) && codeByEndpoint.has(link.target)) {
176172
+ addEdge(link.source, link.target);
176173
+ }
176174
+ }
176175
+ }
176176
+ return { codeByEndpoint, docByEndpoint, counterparts };
176177
+ }
176178
+ function counterpartsOf(graph, endpoint) {
176179
+ const targets = graph.counterparts.get(endpoint);
176180
+ if (targets === undefined) {
176181
+ return [];
176182
+ }
176183
+ const resolved = [];
176184
+ for (const target of targets) {
176185
+ const code = graph.codeByEndpoint.get(target);
176186
+ if (code !== undefined) {
176187
+ resolved.push(code);
176188
+ continue;
176189
+ }
176190
+ const doc = graph.docByEndpoint.get(target);
176191
+ if (doc !== undefined) {
176192
+ resolved.push(doc);
176193
+ }
176194
+ }
176195
+ return resolved.toSorted(compareEndpoints);
176196
+ }
176197
+ function addTo(map, key, value) {
176198
+ const existing = map.get(key);
176199
+ if (existing === undefined) {
176200
+ map.set(key, new Set([value]));
176201
+ return;
176202
+ }
176203
+ existing.add(value);
176204
+ }
176205
+ function compareEndpoints(left, right) {
176206
+ return compareEndpointOrder({ ...left.location, endpoint: left.endpoint }, { ...right.location, endpoint: right.endpoint });
176207
+ }
176208
+
176209
+ // src/core/code-adapter-registry.ts
176210
+ var codeAdapters = {};
176211
+
176212
+ // src/core/scanner-executable.ts
176213
+ import { accessSync, chmodSync, constants, existsSync, realpathSync, statSync } from "node:fs";
176214
+ import { dirname, join as join3, resolve } from "node:path";
176215
+ import { fileURLToPath } from "node:url";
176216
+
176217
+ // src/core/error.ts
176218
+ function reasonOf(error) {
176219
+ return error instanceof Error ? error.message : String(error);
176220
+ }
176221
+
176222
+ // src/core/scanner-executable.ts
176223
+ var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
176224
+ var SCANNER_EXECUTABLE_NAMES = {
176225
+ swift: "docbridge-swift-scanner",
176226
+ dart: "docbridge_dart_scanner",
176227
+ rust: "docbridge-rust-scanner"
176228
+ };
176229
+ function scannerPlatformKey() {
176230
+ return `${process.platform}-${process.arch}`;
176231
+ }
176232
+ function scannerExecutableName(language) {
176233
+ return SCANNER_EXECUTABLE_NAMES[language];
176234
+ }
176235
+ function resolveScannerWorkerCommand(language, options = {}) {
176236
+ const platformKey = options.platformKey ?? scannerPlatformKey();
176237
+ const platformSupported = isSupportedScannerPlatformKey(platformKey);
176238
+ const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
176239
+ const found = candidates.find((candidate) => existsSync(candidate));
176240
+ if (found !== undefined) {
176241
+ const repair = ensureExecutable(found, options.chmod ?? chmodSync);
176242
+ if (!repair.ok) {
176243
+ return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
176244
+ }
176245
+ return { ok: true, command: [found] };
176246
+ }
176247
+ if (!platformSupported) {
176248
+ return {
176249
+ ok: false,
176250
+ diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
176251
+ };
176252
+ }
176253
+ return {
176254
+ ok: false,
176255
+ diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
176256
+ };
176257
+ }
176258
+ function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
176259
+ const sourceRoot = options.sourceRoot ?? sourceRootPath();
176260
+ const distRoot = options.distRoot ?? distRootPath();
176261
+ const executable = scannerExecutableName(language);
176262
+ if (language === "swift") {
176263
+ return [
176264
+ join3(sourceRoot, "packages/swift-scanner/.build/release", executable),
176265
+ join3(sourceRoot, "packages/swift-scanner/.build/debug", executable),
176266
+ ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176267
+ ];
176268
+ }
176269
+ if (language === "rust") {
176270
+ return [
176271
+ join3(sourceRoot, "packages/rust-scanner/target/release", executable),
176272
+ join3(sourceRoot, "packages/rust-scanner/target/debug", executable),
176273
+ ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176274
+ ];
176275
+ }
176276
+ return [
176277
+ join3(sourceRoot, "packages/dart-scanner/bin", executable),
176278
+ ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176279
+ ];
176280
+ }
176281
+ function ensureExecutable(path, chmod) {
176282
+ let mode;
176283
+ try {
176284
+ mode = statSync(path).mode;
176285
+ } catch (error) {
176286
+ return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
176287
+ }
176288
+ if (isExecutableByThisProcess(path)) {
176289
+ return { ok: true };
176290
+ }
176291
+ try {
176292
+ chmod(path, mode | 73);
176293
+ } catch (error) {
176294
+ return {
176295
+ ok: false,
176296
+ reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
176297
+ };
176298
+ }
176299
+ return { ok: true };
176300
+ }
176301
+ function isExecutableByThisProcess(path) {
176302
+ try {
176303
+ accessSync(path, constants.X_OK);
176304
+ return true;
176305
+ } catch {
176306
+ return false;
176307
+ }
176308
+ }
176309
+ function formatMode(mode) {
176310
+ return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
176311
+ }
176312
+ function isSupportedScannerPlatformKey(platformKey) {
176313
+ return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
176314
+ }
176315
+ function scannerRootsFromModuleUrl(moduleUrl) {
176316
+ const modulePath = fileURLToPath(moduleUrl);
176317
+ let resolved;
176318
+ try {
176319
+ resolved = realpathSync(modulePath);
176320
+ } catch {
176321
+ resolved = modulePath;
176322
+ }
176323
+ const moduleDir = dirname(resolved);
176324
+ return { distRoot: moduleDir, sourceRoot: resolve(moduleDir, "..", "..") };
176325
+ }
176326
+ function sourceRootPath() {
176327
+ return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
176328
+ }
176329
+ function distRootPath() {
176330
+ return scannerRootsFromModuleUrl(import.meta.url).distRoot;
176331
+ }
176332
+ function scannerUnavailableDiagnostic(language, reason) {
176333
+ const label = language.charAt(0).toUpperCase() + language.slice(1);
176334
+ return {
176335
+ severity: "error",
176336
+ code: "code_scanner_unavailable",
176337
+ language,
176338
+ target: language,
176339
+ message: `${label} scanner worker is unavailable: ${reason}`
176340
+ };
176341
+ }
176342
+
175805
176343
  // src/core/scanner-worker.ts
175806
176344
  var import__2020 = __toESM(require_2020(), 1);
175807
176345
  import { spawnSync } from "node:child_process";
175808
176346
  import { mkdirSync } from "node:fs";
175809
176347
  import { tmpdir } from "node:os";
175810
- import { dirname, join as join2 } from "node:path";
176348
+ import { dirname as dirname2, join as join4 } from "node:path";
175811
176349
  // schemas/common-output.schema.json
175812
176350
  var common_output_schema_default = {
175813
176351
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -175897,6 +176435,7 @@ var scanner_worker_schema_default = {
175897
176435
  canonicalId: { type: "string" },
175898
176436
  endpoint: { type: "string" },
175899
176437
  location: { $ref: "common-output.schema.json#/$defs/location" },
176438
+ isMember: { type: "boolean" },
175900
176439
  nameRange: { $ref: "common-output.schema.json#/$defs/range" },
175901
176440
  declarationRange: { $ref: "common-output.schema.json#/$defs/range" },
175902
176441
  signatureRange: { $ref: "common-output.schema.json#/$defs/range" }
@@ -176015,7 +176554,7 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
176015
176554
  if (!processResult.ok) {
176016
176555
  return {
176017
176556
  ok: false,
176018
- diagnostic: scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
176557
+ diagnostic: scannerUnavailableDiagnostic2(request.language, processResult.error, command[0]),
176019
176558
  stderr: processResult.stderr
176020
176559
  };
176021
176560
  }
@@ -176056,7 +176595,7 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
176056
176595
  }
176057
176596
  function clangModuleCachePath() {
176058
176597
  const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
176059
- return join2(tmpdir(), `docbridge-clang-module-cache-${owner}`);
176598
+ return join4(tmpdir(), `docbridge-clang-module-cache-${owner}`);
176060
176599
  }
176061
176600
  function runScannerWorkerProcess(input) {
176062
176601
  try {
@@ -176132,7 +176671,7 @@ function responseFilesMatchRequest(responseFiles, requestFiles) {
176132
176671
  function isRecord(value) {
176133
176672
  return typeof value === "object" && value !== null && !Array.isArray(value);
176134
176673
  }
176135
- function scannerUnavailableDiagnostic(language, error, executable) {
176674
+ function scannerUnavailableDiagnostic2(language, error, executable) {
176136
176675
  const label = languageLabel(language);
176137
176676
  return {
176138
176677
  severity: "error",
@@ -176147,7 +176686,7 @@ function spawnFailureReason(error, executable) {
176147
176686
  if (!isExecDenied(error) || executable === undefined) {
176148
176687
  return reason;
176149
176688
  }
176150
- return `${reason}; the scanner is executable but ${dirname(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
176689
+ return `${reason}; the scanner is executable but ${dirname2(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
176151
176690
  }
176152
176691
  function isExecDenied(error) {
176153
176692
  if (!(error instanceof Error)) {
@@ -176283,7 +176822,7 @@ function scanTypeScript(filePath, content, options = {}) {
176283
176822
  for (const declaration of declarations) {
176284
176823
  const endpoint = `${filePath}#${declaration.canonicalId}`;
176285
176824
  if (!documentedEndpoints.has(endpoint)) {
176286
- if (!declaration.isMember && !undocumentedSeen.has(endpoint)) {
176825
+ if (!undocumentedSeen.has(endpoint)) {
176287
176826
  undocumentedSeen.add(endpoint);
176288
176827
  undocumentedSymbols.push(makeCodeSymbol(filePath, endpoint, declaration));
176289
176828
  }
@@ -176609,6 +177148,9 @@ function makeCodeSymbol(filePath, endpoint, declaration) {
176609
177148
  endpoint,
176610
177149
  location: declaration.location
176611
177150
  };
177151
+ if (declaration.isMember) {
177152
+ symbol.isMember = true;
177153
+ }
176612
177154
  if (declaration.nameRange !== undefined) {
176613
177155
  symbol.nameRange = declaration.nameRange;
176614
177156
  }
@@ -176693,54 +177235,13 @@ function duplicateLinkDiagnostic(source, target, location, range) {
176693
177235
  return diagnostic;
176694
177236
  }
176695
177237
 
176696
- // src/core/code-language.ts
176697
- var KNOWN_CODE_LANGUAGES = [
176698
- "typescript",
176699
- "swift",
176700
- "dart",
176701
- "rust"
176702
- ];
176703
- function isCodeLanguage(value) {
176704
- return KNOWN_CODE_LANGUAGES.includes(value);
176705
- }
177238
+ // src/core/code-scan.ts
176706
177239
  Object.assign(codeAdapters, {
176707
177240
  typescript: typeScriptAdapter,
176708
177241
  swift: createScannerWorkerAdapter("swift", (_projectRoot) => resolveScannerWorkerCommand("swift")),
176709
177242
  dart: createScannerWorkerAdapter("dart", (_projectRoot) => resolveScannerWorkerCommand("dart")),
176710
177243
  rust: createScannerWorkerAdapter("rust", (_projectRoot) => resolveScannerWorkerCommand("rust"))
176711
177244
  });
176712
- var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
176713
- var SCANNER_EXECUTABLE_NAMES = {
176714
- swift: "docbridge-swift-scanner",
176715
- dart: "docbridge_dart_scanner",
176716
- rust: "docbridge-rust-scanner"
176717
- };
176718
- function scannerPlatformKey() {
176719
- return `${process.platform}-${process.arch}`;
176720
- }
176721
- function resolveScannerWorkerCommand(language, options = {}) {
176722
- const platformKey = options.platformKey ?? scannerPlatformKey();
176723
- const platformSupported = isSupportedScannerPlatformKey(platformKey);
176724
- const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
176725
- const found = candidates.find((candidate) => existsSync(candidate));
176726
- if (found !== undefined) {
176727
- const repair = ensureExecutable(found, options.chmod ?? chmodSync);
176728
- if (!repair.ok) {
176729
- return { ok: false, diagnostic: scannerUnavailableDiagnostic2(language, repair.reason) };
176730
- }
176731
- return { ok: true, command: [found] };
176732
- }
176733
- if (!platformSupported) {
176734
- return {
176735
- ok: false,
176736
- diagnostic: scannerUnavailableDiagnostic2(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
176737
- };
176738
- }
176739
- return {
176740
- ok: false,
176741
- diagnostic: scannerUnavailableDiagnostic2(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
176742
- };
176743
- }
176744
177245
  function getCodeAdapter(language) {
176745
177246
  return codeAdapters[language];
176746
177247
  }
@@ -176774,25 +177275,6 @@ function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
176774
177275
  }
176775
177276
  };
176776
177277
  }
176777
- function collectCodeFiles(projectRoot, codeInclude) {
176778
- const collected = [];
176779
- const seen = new Set;
176780
- for (const language of KNOWN_CODE_LANGUAGES) {
176781
- const entry = codeInclude[language];
176782
- if (entry === undefined) {
176783
- continue;
176784
- }
176785
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
176786
- if (seen.has(relPath)) {
176787
- continue;
176788
- }
176789
- seen.add(relPath);
176790
- collected.push({ language, relPath });
176791
- }
176792
- }
176793
- collected.sort((left, right) => comparePaths(left.relPath, right.relPath));
176794
- return collected;
176795
- }
176796
177278
  function scanCodeFiles(projectRoot, files, codeInclude, read, onContent) {
176797
177279
  const codeFiles = [];
176798
177280
  const diagnostics = [];
@@ -176817,24 +177299,6 @@ function scanCodeFiles(projectRoot, files, codeInclude, read, onContent) {
176817
177299
  }
176818
177300
  return { codeFiles, diagnostics };
176819
177301
  }
176820
- function codeFileOwners(projectRoot, codeInclude) {
176821
- const owners = new Map;
176822
- for (const language of KNOWN_CODE_LANGUAGES) {
176823
- const entry = codeInclude[language];
176824
- if (entry === undefined) {
176825
- continue;
176826
- }
176827
- for (const relPath of collectFiles(projectRoot, entry.patterns)) {
176828
- const existing = owners.get(relPath);
176829
- if (existing === undefined) {
176830
- owners.set(relPath, [language]);
176831
- } else if (!existing.includes(language)) {
176832
- existing.push(language);
176833
- }
176834
- }
176835
- }
176836
- return owners;
176837
- }
176838
177302
  function emptyScan(language, filePath) {
176839
177303
  return {
176840
177304
  language,
@@ -176851,448 +177315,717 @@ function fileScopedScannerDiagnostic(diagnostic, filePath) {
176851
177315
  function normalizeScannerCommand(value) {
176852
177316
  return Array.isArray(value) ? { ok: true, command: value } : value;
176853
177317
  }
176854
- function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
176855
- const sourceRoot = options.sourceRoot ?? sourceRootPath();
176856
- const distRoot = options.distRoot ?? distRootPath();
176857
- const executable = scannerExecutableName(language);
176858
- if (language === "swift") {
176859
- return [
176860
- join3(sourceRoot, "packages/swift-scanner/.build/release", executable),
176861
- join3(sourceRoot, "packages/swift-scanner/.build/debug", executable),
176862
- ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176863
- ];
176864
- }
176865
- if (language === "rust") {
176866
- return [
176867
- join3(sourceRoot, "packages/rust-scanner/target/release", executable),
176868
- join3(sourceRoot, "packages/rust-scanner/target/debug", executable),
176869
- ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176870
- ];
177318
+
177319
+ // src/core/link-manifest.ts
177320
+ import { readFileSync as readFileSync3 } from "node:fs";
177321
+ import { join as join5 } from "node:path";
177322
+
177323
+ // src/core/json-source.ts
177324
+ class JsonSyntaxError extends Error {
177325
+ position;
177326
+ constructor(message, position) {
177327
+ super(message);
177328
+ this.position = position;
176871
177329
  }
176872
- return [
176873
- join3(sourceRoot, "packages/dart-scanner/bin", executable),
176874
- ...platformSupported ? [join3(distRoot, "bin", platformKey, executable)] : []
176875
- ];
176876
177330
  }
176877
- function ensureExecutable(path, chmod) {
176878
- let mode;
177331
+ var ESCAPE_VALUES = {
177332
+ '"': '"',
177333
+ "\\": "\\",
177334
+ "/": "/",
177335
+ b: "\b",
177336
+ f: "\f",
177337
+ n: `
177338
+ `,
177339
+ r: "\r",
177340
+ t: "\t"
177341
+ };
177342
+ function parseJsonSource(text) {
177343
+ const reader = new JsonReader(text);
176879
177344
  try {
176880
- mode = statSync(path).mode;
177345
+ reader.skipWhitespace();
177346
+ const root = reader.readValue();
177347
+ reader.skipWhitespace();
177348
+ if (!reader.atEnd()) {
177349
+ reader.fail("Unexpected trailing content after the JSON value.");
177350
+ }
177351
+ return { ok: true, root };
176881
177352
  } catch (error) {
176882
- return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
177353
+ if (error instanceof JsonSyntaxError) {
177354
+ return { ok: false, message: error.message, position: error.position };
177355
+ }
177356
+ throw error;
176883
177357
  }
176884
- if (isExecutableByThisProcess(path)) {
176885
- return { ok: true };
177358
+ }
177359
+
177360
+ class JsonReader {
177361
+ text;
177362
+ index = 0;
177363
+ line = 1;
177364
+ column = 1;
177365
+ constructor(text) {
177366
+ this.text = text;
177367
+ }
177368
+ atEnd() {
177369
+ return this.index >= this.text.length;
177370
+ }
177371
+ fail(message) {
177372
+ throw new JsonSyntaxError(message, this.position());
177373
+ }
177374
+ position() {
177375
+ return { line: this.line, column: this.column };
177376
+ }
177377
+ skipWhitespace() {
177378
+ while (!this.atEnd()) {
177379
+ const char = this.text[this.index] ?? "";
177380
+ if (char !== " " && char !== "\t" && char !== `
177381
+ ` && char !== "\r") {
177382
+ return;
177383
+ }
177384
+ this.advance();
177385
+ }
177386
+ }
177387
+ readValue() {
177388
+ if (this.atEnd()) {
177389
+ this.fail("Unexpected end of input where a JSON value was expected.");
177390
+ }
177391
+ const char = this.text[this.index] ?? "";
177392
+ switch (char) {
177393
+ case "{":
177394
+ return this.readObject();
177395
+ case "[":
177396
+ return this.readArray();
177397
+ case '"':
177398
+ return this.readString();
177399
+ case "t":
177400
+ return this.readKeyword("true", true);
177401
+ case "f":
177402
+ return this.readKeyword("false", false);
177403
+ case "n":
177404
+ return this.readNull();
177405
+ default:
177406
+ return this.readNumber();
177407
+ }
176886
177408
  }
176887
- try {
176888
- chmod(path, mode | 73);
176889
- } catch (error) {
176890
- return {
176891
- ok: false,
176892
- reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
176893
- };
177409
+ readObject() {
177410
+ const start = this.position();
177411
+ this.expect("{");
177412
+ const members = [];
177413
+ this.skipWhitespace();
177414
+ if (this.peek() === "}") {
177415
+ this.advance();
177416
+ return { kind: "object", members, range: { start, end: this.position() } };
177417
+ }
177418
+ for (;; ) {
177419
+ this.skipWhitespace();
177420
+ if (this.peek() !== '"') {
177421
+ this.fail("Expected a quoted object key.");
177422
+ }
177423
+ const keyNode = this.readString();
177424
+ this.skipWhitespace();
177425
+ this.expect(":");
177426
+ this.skipWhitespace();
177427
+ const value = this.readValue();
177428
+ members.push({ key: keyNode.value, keyRange: keyNode.range, value });
177429
+ this.skipWhitespace();
177430
+ const next = this.peek();
177431
+ if (next === ",") {
177432
+ this.advance();
177433
+ continue;
177434
+ }
177435
+ if (next === "}") {
177436
+ this.advance();
177437
+ return { kind: "object", members, range: { start, end: this.position() } };
177438
+ }
177439
+ this.fail("Expected `,` or `}` in the object.");
177440
+ }
176894
177441
  }
176895
- return { ok: true };
176896
- }
176897
- function isExecutableByThisProcess(path) {
176898
- try {
176899
- accessSync(path, constants.X_OK);
176900
- return true;
176901
- } catch {
176902
- return false;
177442
+ readArray() {
177443
+ const start = this.position();
177444
+ this.expect("[");
177445
+ const elements = [];
177446
+ this.skipWhitespace();
177447
+ if (this.peek() === "]") {
177448
+ this.advance();
177449
+ return { kind: "array", elements, range: { start, end: this.position() } };
177450
+ }
177451
+ for (;; ) {
177452
+ this.skipWhitespace();
177453
+ elements.push(this.readValue());
177454
+ this.skipWhitespace();
177455
+ const next = this.peek();
177456
+ if (next === ",") {
177457
+ this.advance();
177458
+ continue;
177459
+ }
177460
+ if (next === "]") {
177461
+ this.advance();
177462
+ return { kind: "array", elements, range: { start, end: this.position() } };
177463
+ }
177464
+ this.fail("Expected `,` or `]` in the array.");
177465
+ }
176903
177466
  }
176904
- }
176905
- function formatMode(mode) {
176906
- return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
176907
- }
176908
- function isSupportedScannerPlatformKey(platformKey) {
176909
- return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
176910
- }
176911
- function scannerRootsFromModuleUrl(moduleUrl) {
176912
- const modulePath = fileURLToPath(moduleUrl);
176913
- let resolved;
176914
- try {
176915
- resolved = realpathSync(modulePath);
176916
- } catch {
176917
- resolved = modulePath;
177467
+ readString() {
177468
+ const start = this.position();
177469
+ this.expect('"');
177470
+ const contentStart = this.position();
177471
+ let value = "";
177472
+ for (;; ) {
177473
+ if (this.atEnd()) {
177474
+ this.fail("Unterminated string.");
177475
+ }
177476
+ const char = this.text[this.index] ?? "";
177477
+ if (char === '"') {
177478
+ const contentEnd = this.position();
177479
+ this.advance();
177480
+ return {
177481
+ kind: "string",
177482
+ value,
177483
+ contentRange: { start: contentStart, end: contentEnd },
177484
+ range: { start, end: this.position() }
177485
+ };
177486
+ }
177487
+ if (char === "\\") {
177488
+ this.advance();
177489
+ value += this.readEscape();
177490
+ continue;
177491
+ }
177492
+ if (char < " ") {
177493
+ this.fail("Unescaped control character in a string.");
177494
+ }
177495
+ this.advance();
177496
+ value += char;
177497
+ }
177498
+ }
177499
+ readEscape() {
177500
+ if (this.atEnd()) {
177501
+ this.fail("Unterminated escape sequence.");
177502
+ }
177503
+ const char = this.text[this.index] ?? "";
177504
+ const simple = ESCAPE_VALUES[char];
177505
+ if (simple !== undefined) {
177506
+ this.advance();
177507
+ return simple;
177508
+ }
177509
+ if (char !== "u") {
177510
+ this.fail(`Unknown escape sequence \`\\${char}\`.`);
177511
+ }
177512
+ this.advance();
177513
+ const digits = this.text.slice(this.index, this.index + 4);
177514
+ if (!/^[0-9a-fA-F]{4}$/.test(digits)) {
177515
+ this.fail("A `\\u` escape needs four hexadecimal digits.");
177516
+ }
177517
+ for (let offset = 0;offset < 4; offset += 1) {
177518
+ this.advance();
177519
+ }
177520
+ return String.fromCharCode(Number.parseInt(digits, 16));
177521
+ }
177522
+ readNumber() {
177523
+ const start = this.position();
177524
+ const pattern = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
177525
+ pattern.lastIndex = this.index;
177526
+ const match = pattern.exec(this.text);
177527
+ if (match === null || match[0].length === 0) {
177528
+ this.fail("Expected a JSON value.");
177529
+ }
177530
+ for (let offset = 0;offset < match[0].length; offset += 1) {
177531
+ this.advance();
177532
+ }
177533
+ if (/[0-9a-zA-Z.+-]/.test(this.peek() ?? "")) {
177534
+ this.fail("Malformed number.");
177535
+ }
177536
+ return { kind: "number", value: Number(match[0]), range: { start, end: this.position() } };
177537
+ }
177538
+ readNull() {
177539
+ const start = this.position();
177540
+ this.consumeWord("null");
177541
+ return { kind: "null", value: null, range: { start, end: this.position() } };
177542
+ }
177543
+ readKeyword(word, value) {
177544
+ const start = this.position();
177545
+ this.consumeWord(word);
177546
+ return { kind: "boolean", value, range: { start, end: this.position() } };
177547
+ }
177548
+ consumeWord(word) {
177549
+ if (this.text.slice(this.index, this.index + word.length) !== word) {
177550
+ this.fail(`Expected \`${word}\`.`);
177551
+ }
177552
+ for (let offset = 0;offset < word.length; offset += 1) {
177553
+ this.advance();
177554
+ }
177555
+ }
177556
+ peek() {
177557
+ return this.text[this.index];
177558
+ }
177559
+ expect(char) {
177560
+ if (this.peek() !== char) {
177561
+ this.fail(`Expected \`${char}\`.`);
177562
+ }
177563
+ this.advance();
177564
+ }
177565
+ advance() {
177566
+ const char = this.text[this.index] ?? "";
177567
+ this.index += 1;
177568
+ if (char === `
177569
+ `) {
177570
+ this.line += 1;
177571
+ this.column = 1;
177572
+ return;
177573
+ }
177574
+ if (char === "\r") {
177575
+ if (this.text[this.index] === `
177576
+ `) {
177577
+ this.index += 1;
177578
+ }
177579
+ this.line += 1;
177580
+ this.column = 1;
177581
+ return;
177582
+ }
177583
+ this.column += 1;
176918
177584
  }
176919
- const moduleDir = dirname2(resolved);
176920
- return { distRoot: moduleDir, sourceRoot: resolve(moduleDir, "..", "..") };
176921
- }
176922
- function sourceRootPath() {
176923
- return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
176924
- }
176925
- function distRootPath() {
176926
- return scannerRootsFromModuleUrl(import.meta.url).distRoot;
176927
- }
176928
- function scannerExecutableName(language) {
176929
- return SCANNER_EXECUTABLE_NAMES[language];
176930
- }
176931
- function scannerUnavailableDiagnostic2(language, reason) {
176932
- const label = language.charAt(0).toUpperCase() + language.slice(1);
176933
- return {
176934
- severity: "error",
176935
- code: "code_scanner_unavailable",
176936
- language,
176937
- target: language,
176938
- message: `${label} scanner worker is unavailable: ${reason}`
176939
- };
176940
177585
  }
176941
177586
 
176942
- // src/core/config.ts
176943
- var CONFIG_FILE_NAME = "docbridge.config.json";
176944
- var EMPTY_CONFIG = {
176945
- include: { code: {}, docs: [] }
176946
- };
176947
- var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
176948
- var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
176949
- var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
176950
- var LANGUAGE_SUFFIX = {
176951
- typescript: ".ts",
176952
- swift: ".swift",
176953
- dart: ".dart",
176954
- rust: ".rs"
176955
- };
176956
- var LANGUAGE_VISIBILITY = {
176957
- typescript: ["public", "protected", "private"],
176958
- swift: ["public", "open", "internal"],
176959
- dart: ["public"],
176960
- rust: ["pub", "private"]
176961
- };
176962
- function loadConfig(projectRoot) {
177587
+ // src/core/link-manifest.ts
177588
+ var LINK_MANIFEST_FILE_NAME = "docbridge.links.json";
177589
+ var KNOWN_TOP_LEVEL_KEYS2 = new Set(["$schema", "links"]);
177590
+ var KNOWN_ENTRY_KEYS = new Set(["code", "doc", "note"]);
177591
+ var EMPTY_MANIFEST = { entries: [] };
177592
+ function loadLinkManifest(projectRoot) {
176963
177593
  let rawText;
176964
177594
  try {
176965
- rawText = readFileSync2(join4(projectRoot, CONFIG_FILE_NAME), "utf8");
176966
- } catch {
177595
+ rawText = readFileSync3(join5(projectRoot, LINK_MANIFEST_FILE_NAME), "utf8");
177596
+ } catch (error) {
177597
+ if (!isMissingFileError(error)) {
177598
+ const reason = error instanceof Error ? error.message : String(error);
177599
+ return {
177600
+ ok: false,
177601
+ diagnostics: [
177602
+ manifestDiagnostic("config_file_invalid", `Failed to read ${LINK_MANIFEST_FILE_NAME}: ${reason}`)
177603
+ ]
177604
+ };
177605
+ }
176967
177606
  rawText = undefined;
176968
177607
  }
176969
- const resolved = resolveConfig(rawText);
176970
- if (!resolved.ok) {
176971
- return resolved;
176972
- }
176973
- const overlapDiagnostics = detectLanguageOverlap(projectRoot, resolved.config.include.code);
176974
- if (overlapDiagnostics.length === 0) {
176975
- return resolved;
176976
- }
176977
- return {
176978
- config: EMPTY_CONFIG,
176979
- diagnostics: [...resolved.diagnostics, ...overlapDiagnostics],
176980
- ok: false
176981
- };
177608
+ return resolveLinkManifest(rawText);
176982
177609
  }
176983
- function resolveConfig(rawText) {
177610
+ function isMissingFileError(error) {
177611
+ return error instanceof Error && error.code === "ENOENT";
177612
+ }
177613
+ function resolveLinkManifest(rawText) {
176984
177614
  if (rawText === undefined) {
176985
- return {
176986
- config: EMPTY_CONFIG,
176987
- ok: false,
176988
- diagnostics: [
176989
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
176990
- ]
176991
- };
177615
+ return { ok: true, manifest: EMPTY_MANIFEST, diagnostics: [] };
176992
177616
  }
176993
- let parsed;
176994
- try {
176995
- parsed = JSON.parse(rawText);
176996
- } catch (error) {
176997
- const reason = error instanceof Error ? error.message : String(error);
177617
+ const parsed = parseJsonSource(rawText);
177618
+ if (!parsed.ok) {
176998
177619
  return {
176999
- config: EMPTY_CONFIG,
177000
177620
  ok: false,
177001
177621
  diagnostics: [
177002
- configDiagnostic("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
177622
+ manifestDiagnostic("config_file_invalid", `Failed to parse ${LINK_MANIFEST_FILE_NAME}: ${parsed.message}`, parsed.position)
177003
177623
  ]
177004
177624
  };
177005
177625
  }
177006
- const diagnostics = [];
177007
- if (!isPlainObject(parsed)) {
177008
- diagnostics.push(configDiagnostic("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
177009
- return { config: EMPTY_CONFIG, diagnostics, ok: false };
177626
+ return validateManifest(parsed.root);
177627
+ }
177628
+ function validateManifest(root) {
177629
+ if (root.kind !== "object") {
177630
+ return { ok: false, diagnostics: [invalidValue("", "an object", root)] };
177010
177631
  }
177011
- for (const key of Object.keys(parsed)) {
177012
- if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
177013
- diagnostics.push(configDiagnostic("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
177632
+ const diagnostics = [];
177633
+ for (const member of root.members) {
177634
+ if (!KNOWN_TOP_LEVEL_KEYS2.has(member.key)) {
177635
+ diagnostics.push(unknownKey(member.key, member));
177014
177636
  }
177015
177637
  }
177016
- if ("$schema" in parsed && typeof parsed.$schema !== "string") {
177017
- diagnostics.push(configDiagnostic("config_invalid_value", "$schema", "`$schema` must be a string."));
177638
+ reportDuplicateKeys(root, "", diagnostics);
177639
+ const links = root.members.find((member) => member.key === "links");
177640
+ if (links === undefined) {
177641
+ return {
177642
+ ok: false,
177643
+ diagnostics: [...diagnostics, invalidValue("links", "present", root)]
177644
+ };
177018
177645
  }
177019
- const include = parsed.include;
177020
- if (!isPlainObject(include)) {
177021
- diagnostics.push(configDiagnostic("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
177022
- return { config: EMPTY_CONFIG, diagnostics, ok: false };
177646
+ if (links.value.kind !== "array") {
177647
+ return {
177648
+ ok: false,
177649
+ diagnostics: [...diagnostics, invalidValue("links", "an array", links.value)]
177650
+ };
177023
177651
  }
177024
- for (const key of Object.keys(include)) {
177025
- if (!KNOWN_INCLUDE_KEYS.has(key)) {
177026
- diagnostics.push(configDiagnostic("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
177652
+ const entries = [];
177653
+ for (const [index, element] of links.value.elements.entries()) {
177654
+ const outcome = readEntry(element, index);
177655
+ diagnostics.push(...outcome.diagnostics);
177656
+ if (outcome.entry !== undefined) {
177657
+ entries.push(outcome.entry);
177027
177658
  }
177028
177659
  }
177029
- const code = validateCodeInclude(include.code, diagnostics);
177030
- validatePatternArray(include.docs, "docs", ".md", false, diagnostics);
177031
- const ok = diagnostics.length === 0;
177032
- const config = ok ? { include: { code, docs: include.docs } } : EMPTY_CONFIG;
177033
- return { config, diagnostics, ok };
177034
- }
177035
- function validateCodeInclude(value, diagnostics) {
177036
- if (Array.isArray(value)) {
177037
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
177038
- return {};
177039
- }
177040
- if (!isPlainObject(value)) {
177041
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
177042
- return {};
177043
- }
177044
- const keys = Object.keys(value);
177045
- if (keys.length === 0) {
177046
- diagnostics.push(configDiagnostic("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
177047
- return {};
177660
+ if (diagnostics.some(isBlocking)) {
177661
+ return { ok: false, diagnostics };
177048
177662
  }
177049
- const result = {};
177050
- for (const key of keys) {
177051
- if (!isCodeLanguage(key)) {
177052
- diagnostics.push(configDiagnostic("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
177053
- continue;
177054
- }
177055
- const entry = validateCodeEntry(key, value[key], diagnostics);
177056
- if (entry !== undefined) {
177057
- result[key] = entry;
177058
- }
177059
- }
177060
- return result;
177663
+ return { ok: true, manifest: { entries }, diagnostics };
177061
177664
  }
177062
- function validateCodeEntry(language, value, diagnostics) {
177063
- const target = `include.code.${language}`;
177064
- if (!isPlainObject(value)) {
177065
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
177066
- return;
177067
- }
177068
- for (const key of Object.keys(value)) {
177069
- if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
177070
- diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
177071
- }
177072
- }
177073
- const before = diagnostics.length;
177074
- validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIX[language], language === "typescript", diagnostics);
177075
- if ("visibility" in value) {
177076
- if (!isStringArray(value.visibility)) {
177077
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
177078
- } else {
177079
- validateVisibilityOptions(language, value.visibility, target, diagnostics);
177080
- }
177081
- }
177082
- if (diagnostics.length !== before) {
177083
- return;
177665
+ function readEntry(element, index) {
177666
+ const path = `links[${index}]`;
177667
+ if (element.kind !== "object") {
177668
+ return { diagnostics: [invalidValue(path, "an object", element)] };
177084
177669
  }
177085
- const entry = { patterns: value.patterns };
177086
- if (isStringArray(value.visibility)) {
177087
- entry.visibility = value.visibility;
177670
+ const diagnostics = [];
177671
+ for (const member of element.members) {
177672
+ if (!KNOWN_ENTRY_KEYS.has(member.key)) {
177673
+ diagnostics.push(unknownKey(`${path}.${member.key}`, member));
177674
+ }
177675
+ }
177676
+ reportDuplicateKeys(element, `${path}.`, diagnostics);
177677
+ const code = readString(element, "code", path, diagnostics);
177678
+ const doc = readString(element, "doc", path, diagnostics);
177679
+ const note = readOptionalNote(element, path, diagnostics);
177680
+ if (code === undefined || doc === undefined || diagnostics.some(isBlocking)) {
177681
+ return { diagnostics };
177682
+ }
177683
+ const codeTarget = parseTarget(code, diagnostics);
177684
+ const docTarget = parseTarget(doc, diagnostics);
177685
+ if (codeTarget === undefined || docTarget === undefined) {
177686
+ return { diagnostics };
177687
+ }
177688
+ const entry = {
177689
+ code: codeTarget,
177690
+ doc: docTarget,
177691
+ codeEndpoint: code.value,
177692
+ docEndpoint: doc.value,
177693
+ location: positionOf(element.range),
177694
+ range: element.range,
177695
+ codeRange: code.contentRange,
177696
+ docRange: doc.contentRange
177697
+ };
177698
+ if (note !== undefined) {
177699
+ entry.note = note;
177088
177700
  }
177089
- return entry;
177701
+ return { entry, diagnostics };
177090
177702
  }
177091
- function validateVisibilityOptions(language, values, target, diagnostics) {
177092
- const allowed = LANGUAGE_VISIBILITY[language];
177093
- if (allowed.length === 0) {
177094
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
177703
+ function readString(entry, key, path, diagnostics) {
177704
+ const member = entry.members.find((candidate) => candidate.key === key);
177705
+ if (member === undefined) {
177706
+ diagnostics.push(invalidValue(`${path}.${key}`, "present", entry));
177095
177707
  return;
177096
177708
  }
177097
- for (const value of values) {
177098
- if (!allowed.includes(value)) {
177099
- diagnostics.push(configDiagnostic("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
177100
- }
177709
+ if (member.value.kind !== "string") {
177710
+ diagnostics.push(invalidValue(`${path}.${key}`, "a string", member.value));
177711
+ return;
177101
177712
  }
177713
+ return member.value;
177102
177714
  }
177103
- function validatePatternArray(value, field, requiredSuffix, excludeDeclarationFiles, diagnostics) {
177104
- const target = `include.${field}`;
177105
- if (!Array.isArray(value)) {
177106
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
177715
+ function readOptionalNote(entry, path, diagnostics) {
177716
+ const member = entry.members.find((candidate) => candidate.key === "note");
177717
+ if (member === undefined) {
177107
177718
  return;
177108
177719
  }
177109
- if (value.length === 0) {
177110
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
177720
+ if (member.value.kind !== "string") {
177721
+ diagnostics.push(invalidValue(`${path}.note`, "a string", member.value));
177111
177722
  return;
177112
177723
  }
177113
- for (const pattern of value) {
177114
- if (typeof pattern !== "string") {
177115
- diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
177116
- continue;
177117
- }
177118
- const globResult = validateGlobPattern(pattern);
177119
- if (!globResult.ok) {
177120
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
177121
- continue;
177122
- }
177123
- const suffixError = checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles);
177124
- if (suffixError !== undefined) {
177125
- diagnostics.push(configDiagnostic("config_invalid_value", pattern, suffixError));
177126
- }
177127
- }
177724
+ return member.value.value;
177128
177725
  }
177129
- function checkSuffix(pattern, requiredSuffix, excludeDeclarationFiles) {
177130
- if (!pattern.endsWith(requiredSuffix)) {
177131
- return `Pattern must end with \`${requiredSuffix}\`.`;
177132
- }
177133
- if (excludeDeclarationFiles && pattern.endsWith(".d.ts")) {
177134
- return "Pattern must not target `.d.ts` declaration files.";
177726
+ function parseTarget(node, diagnostics) {
177727
+ const result = parseLinkTarget(node.value, {
177728
+ location: positionOf(node.contentRange),
177729
+ targetRange: node.contentRange
177730
+ });
177731
+ if (result.ok) {
177732
+ return result.target;
177135
177733
  }
177734
+ diagnostics.push(result.diagnostic);
177136
177735
  return;
177137
177736
  }
177138
- function detectLanguageOverlap(projectRoot, code) {
177139
- const diagnostics = [];
177140
- for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
177141
- if (owners.length > 1) {
177142
- diagnostics.push(configDiagnostic("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
177737
+ function reportDuplicateKeys(object, pathPrefix, diagnostics) {
177738
+ const seen = new Set;
177739
+ for (const member of object.members) {
177740
+ if (seen.has(member.key)) {
177741
+ diagnostics.push(manifestDiagnostic("config_invalid_value", `Duplicate key ${pathPrefix}${member.key} in ${LINK_MANIFEST_FILE_NAME}.`, positionOf(member.keyRange), member.keyRange));
177143
177742
  }
177743
+ seen.add(member.key);
177144
177744
  }
177145
- return diagnostics;
177146
177745
  }
177147
- function configDiagnostic(code, target, message) {
177148
- return { severity: "error", code, target, message };
177149
- }
177150
- function isPlainObject(value) {
177151
- return typeof value === "object" && value !== null && !Array.isArray(value);
177746
+ function isBlocking(diagnostic) {
177747
+ return diagnostic.code !== "invalid_link_target";
177152
177748
  }
177153
- function isStringArray(value) {
177154
- return Array.isArray(value) && value.every((item) => typeof item === "string");
177749
+ function unknownKey(path, member) {
177750
+ return manifestDiagnostic("config_unknown_key", `Unknown key ${path} in ${LINK_MANIFEST_FILE_NAME}.`, positionOf(member.keyRange), member.keyRange);
177155
177751
  }
177156
-
177157
- // src/core/diagnostics.ts
177158
- function sortDiagnostics(diagnostics) {
177159
- return [...diagnostics].toSorted(compareDiagnostics);
177752
+ function invalidValue(path, expectation, node) {
177753
+ const subject = path === "" ? LINK_MANIFEST_FILE_NAME : path;
177754
+ return manifestDiagnostic("config_invalid_value", `${subject} must be ${expectation}.`, positionOf(node.range), node.range);
177160
177755
  }
177161
- function summarizeDiagnostics(diagnostics) {
177162
- const summary = { errors: 0, warnings: 0 };
177163
- for (const diagnostic of diagnostics) {
177164
- if (diagnostic.severity === "error") {
177165
- summary.errors += 1;
177166
- } else {
177167
- summary.warnings += 1;
177168
- }
177756
+ function manifestDiagnostic(code, message, position, range) {
177757
+ const diagnostic = {
177758
+ severity: "error",
177759
+ code,
177760
+ target: LINK_MANIFEST_FILE_NAME,
177761
+ message
177762
+ };
177763
+ if (position !== undefined) {
177764
+ diagnostic.location = {
177765
+ filePath: LINK_MANIFEST_FILE_NAME,
177766
+ line: position.line,
177767
+ column: position.column
177768
+ };
177169
177769
  }
177170
- return summary;
177171
- }
177172
- function formatDiagnostic(diagnostic) {
177173
- const location = diagnostic.location;
177174
- if (location === undefined) {
177175
- return `${diagnostic.target} ${diagnostic.severity} ${diagnostic.code} - ${diagnostic.message}`;
177770
+ if (range !== undefined) {
177771
+ diagnostic.range = range;
177176
177772
  }
177177
- const prefix = `${location.filePath}:${location.line}:${location.column}`;
177178
- return `${prefix} ${diagnostic.severity} ${diagnostic.code} ${diagnostic.target} - ${diagnostic.message}`;
177773
+ return diagnostic;
177179
177774
  }
177180
- function formatSummary(summary) {
177181
- return `Summary: ${summary.errors} ${pluralize("error", summary.errors)}, ${summary.warnings} ${pluralize("warning", summary.warnings)}`;
177775
+ function positionOf(range) {
177776
+ return {
177777
+ filePath: LINK_MANIFEST_FILE_NAME,
177778
+ line: range.start.line,
177779
+ column: range.start.column
177780
+ };
177182
177781
  }
177183
- function compareDiagnostics(left, right) {
177184
- const leftLocation = left.location;
177185
- const rightLocation = right.location;
177186
- if (leftLocation === undefined && rightLocation !== undefined) {
177187
- return -1;
177188
- }
177189
- if (leftLocation !== undefined && rightLocation === undefined) {
177190
- return 1;
177782
+
177783
+ // src/core/suggest.ts
177784
+ function nearestMatch(input, candidates) {
177785
+ const ranked = candidates.map((candidate, index) => ({
177786
+ candidate,
177787
+ distance: editDistance(input, candidate),
177788
+ index
177789
+ })).toSorted((left, right) => left.distance - right.distance || left.index - right.index);
177790
+ const abbreviation = ranked.find(({ candidate }) => isOrderedAbbreviation(input, candidate));
177791
+ if (abbreviation !== undefined) {
177792
+ return abbreviation.candidate;
177191
177793
  }
177192
- if (leftLocation !== undefined && rightLocation !== undefined) {
177193
- return compareString(leftLocation.filePath, rightLocation.filePath) || compareNumber(leftLocation.line, rightLocation.line) || compareNumber(leftLocation.column, rightLocation.column) || compareString(left.code, right.code) || compareString(left.target, right.target);
177794
+ const best = ranked[0];
177795
+ if (best === undefined) {
177796
+ return;
177194
177797
  }
177195
- return compareString(left.code, right.code) || compareString(left.target, right.target);
177196
- }
177197
- function compareString(left, right) {
177198
- return left.localeCompare(right);
177798
+ const closeEnough = best.distance <= Math.max(1, Math.floor(best.candidate.length / 2));
177799
+ return closeEnough ? best.candidate : undefined;
177199
177800
  }
177200
- function compareNumber(left, right) {
177201
- return left - right;
177801
+ function isOrderedAbbreviation(input, candidate) {
177802
+ if (input.length < 3 || input.length >= candidate.length) {
177803
+ return false;
177804
+ }
177805
+ let candidateIndex = 0;
177806
+ for (const character of input) {
177807
+ const matchIndex = candidate.indexOf(character, candidateIndex);
177808
+ if (matchIndex === -1) {
177809
+ return false;
177810
+ }
177811
+ candidateIndex = matchIndex + 1;
177812
+ }
177813
+ return true;
177202
177814
  }
177203
- function pluralize(word, count) {
177204
- return count === 1 ? word : `${word}s`;
177815
+ function editDistance(left, right) {
177816
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
177817
+ for (let leftIndex = 1;leftIndex <= left.length; leftIndex += 1) {
177818
+ const current = [leftIndex];
177819
+ for (let rightIndex = 1;rightIndex <= right.length; rightIndex += 1) {
177820
+ const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
177821
+ current[rightIndex] = Math.min((current[rightIndex - 1] ?? Number.POSITIVE_INFINITY) + 1, (previous[rightIndex] ?? Number.POSITIVE_INFINITY) + 1, (previous[rightIndex - 1] ?? Number.POSITIVE_INFINITY) + substitutionCost);
177822
+ }
177823
+ previous = current;
177824
+ }
177825
+ return previous[right.length] ?? 0;
177205
177826
  }
177206
177827
 
177207
- // src/core/endpoint.ts
177208
- function filePathOf(endpoint) {
177209
- const hashIndex = endpoint.indexOf("#");
177210
- return hashIndex === -1 ? endpoint : endpoint.slice(0, hashIndex);
177211
- }
177212
- function fragmentOf(endpoint) {
177213
- const hashIndex = endpoint.indexOf("#");
177214
- return hashIndex === -1 ? endpoint : endpoint.slice(hashIndex + 1);
177215
- }
177216
- function compareEndpointOrder(left, right) {
177217
- return left.filePath.localeCompare(right.filePath) || left.line - right.line || left.column - right.column || left.endpoint.localeCompare(right.endpoint);
177218
- }
177219
- function endpointRange(element) {
177220
- const range = element.kind === "code" ? element.nameRange : element.headingTextRange;
177221
- if (range !== undefined) {
177222
- return range;
177828
+ // src/core/link-manifest-apply.ts
177829
+ function applyLinkManifest(input) {
177830
+ if (input.manifest.entries.length === 0) {
177831
+ return { codeFiles: input.codeFiles, docFiles: input.docFiles, diagnostics: [] };
177223
177832
  }
177224
- const { line, column } = element.location;
177225
- return { start: { line, column }, end: { line, column } };
177833
+ const state = new ApplyState(input);
177834
+ for (const entry of input.manifest.entries) {
177835
+ state.applyEntry(entry);
177836
+ }
177837
+ return state.result();
177226
177838
  }
177227
177839
 
177228
- // src/core/graph.ts
177229
- function buildLinkGraph(codeFiles, docFiles) {
177230
- const codeByEndpoint = new Map;
177231
- for (const file of codeFiles) {
177232
- for (const symbol of file.symbols) {
177233
- if (!codeByEndpoint.has(symbol.endpoint)) {
177234
- codeByEndpoint.set(symbol.endpoint, symbol);
177235
- }
177840
+ class ApplyState {
177841
+ erroredFiles;
177842
+ codeStates = new Map;
177843
+ docStates = new Map;
177844
+ codeOrder;
177845
+ docOrder;
177846
+ diagnostics = [];
177847
+ constructor(input) {
177848
+ this.erroredFiles = collectErroredFiles(input.scanDiagnostics);
177849
+ this.codeOrder = input.codeFiles;
177850
+ this.docOrder = input.docFiles;
177851
+ for (const file of input.codeFiles) {
177852
+ const byEndpoint = new Map;
177853
+ for (const symbol of [...file.symbols, ...file.undocumentedSymbols]) {
177854
+ if (!byEndpoint.has(symbol.endpoint)) {
177855
+ byEndpoint.set(symbol.endpoint, symbol);
177856
+ }
177857
+ }
177858
+ this.codeStates.set(file.filePath, {
177859
+ file,
177860
+ symbols: file.symbols,
177861
+ undocumentedSymbols: file.undocumentedSymbols,
177862
+ links: file.links,
177863
+ byEndpoint,
177864
+ outgoing: new Set(file.links.map((annotation) => pairKey(annotation.source, annotation.target))),
177865
+ changed: false
177866
+ });
177867
+ }
177868
+ for (const file of input.docFiles) {
177869
+ this.docStates.set(file.filePath, {
177870
+ file,
177871
+ headings: file.headings,
177872
+ links: file.links,
177873
+ anchors: new Set(file.anchors.map((anchor) => anchor.endpoint)),
177874
+ outgoing: new Set(file.links.map((annotation) => pairKey(annotation.source, annotation.target))),
177875
+ changed: false
177876
+ });
177236
177877
  }
177237
177878
  }
177238
- const docByEndpoint = new Map;
177239
- for (const file of docFiles) {
177240
- for (const anchor of file.anchors) {
177241
- if (!docByEndpoint.has(anchor.endpoint)) {
177242
- docByEndpoint.set(anchor.endpoint, anchor);
177243
- }
177879
+ applyEntry(entry) {
177880
+ if (this.erroredFiles.has(entry.code.filePath) || this.erroredFiles.has(entry.doc.filePath)) {
177881
+ return;
177244
177882
  }
177883
+ const codeResolved = this.resolveCodeSide(entry);
177884
+ const docResolved = this.resolveDocSide(entry);
177885
+ if (!codeResolved || !docResolved) {
177886
+ return;
177887
+ }
177888
+ this.addLinks(entry);
177245
177889
  }
177246
- const counterparts = new Map;
177247
- const addEdge = (a, b) => {
177248
- addTo(counterparts, a, b);
177249
- addTo(counterparts, b, a);
177250
- };
177251
- for (const file of codeFiles) {
177252
- for (const link of file.links) {
177253
- if (codeByEndpoint.has(link.source) && docByEndpoint.has(link.target)) {
177254
- addEdge(link.source, link.target);
177255
- }
177890
+ resolveCodeSide(entry) {
177891
+ const state = this.codeStates.get(entry.code.filePath);
177892
+ if (state === undefined) {
177893
+ this.report("code_file_not_found", entry.codeEndpoint, `Code file ${entry.code.filePath} declared in the link manifest is not in the managed code set.`, entry, "code");
177894
+ return false;
177256
177895
  }
177896
+ const symbol = state.byEndpoint.get(entry.codeEndpoint);
177897
+ if (symbol === undefined) {
177898
+ this.report("code_symbol_not_found", entry.codeEndpoint, this.symbolNotFoundMessage(entry, state), entry, "code", state.file.language);
177899
+ return false;
177900
+ }
177901
+ this.markDocumented(state, symbol);
177902
+ return true;
177257
177903
  }
177258
- for (const file of docFiles) {
177259
- for (const link of file.links) {
177260
- if (docByEndpoint.has(link.source) && codeByEndpoint.has(link.target)) {
177261
- addEdge(link.source, link.target);
177262
- }
177904
+ resolveDocSide(entry) {
177905
+ const state = this.docStates.get(entry.doc.filePath);
177906
+ if (state === undefined) {
177907
+ this.report("doc_file_not_found", entry.docEndpoint, `Doc file ${entry.doc.filePath} declared in the link manifest is not in the managed docs set.`, entry, "doc");
177908
+ return false;
177263
177909
  }
177910
+ if (!state.anchors.has(entry.docEndpoint)) {
177911
+ this.report("doc_anchor_not_found", entry.docEndpoint, `Doc anchor ${entry.docEndpoint} declared in the link manifest does not exist.`, entry, "doc");
177912
+ return false;
177913
+ }
177914
+ this.markAnnotated(state, entry.docEndpoint);
177915
+ return true;
177264
177916
  }
177265
- return { codeByEndpoint, docByEndpoint, counterparts };
177266
- }
177267
- function counterpartsOf(graph, endpoint) {
177268
- const targets = graph.counterparts.get(endpoint);
177269
- if (targets === undefined) {
177270
- return [];
177917
+ addLinks(entry) {
177918
+ const codeState = this.codeStates.get(entry.code.filePath);
177919
+ const docState = this.docStates.get(entry.doc.filePath);
177920
+ if (codeState === undefined || docState === undefined) {
177921
+ return;
177922
+ }
177923
+ const forward = pairKey(entry.codeEndpoint, entry.docEndpoint);
177924
+ const backward = pairKey(entry.docEndpoint, entry.codeEndpoint);
177925
+ const hasForward = codeState.outgoing.has(forward);
177926
+ const hasBackward = docState.outgoing.has(backward);
177927
+ if (hasForward || hasBackward) {
177928
+ this.diagnostics.push({
177929
+ severity: "warning",
177930
+ code: "duplicate_link",
177931
+ source: entry.codeEndpoint,
177932
+ target: entry.docEndpoint,
177933
+ message: `Link ${entry.codeEndpoint} to ${entry.docEndpoint} is already declared.`,
177934
+ location: entry.location,
177935
+ range: entry.range
177936
+ });
177937
+ }
177938
+ if (!hasForward) {
177939
+ codeState.links = [
177940
+ ...codeState.links,
177941
+ manifestLink(entry.codeEndpoint, entry.docEndpoint, entry, "doc")
177942
+ ];
177943
+ codeState.outgoing.add(forward);
177944
+ codeState.changed = true;
177945
+ }
177946
+ if (!hasBackward) {
177947
+ docState.links = [
177948
+ ...docState.links,
177949
+ manifestLink(entry.docEndpoint, entry.codeEndpoint, entry, "code")
177950
+ ];
177951
+ docState.outgoing.add(backward);
177952
+ docState.changed = true;
177953
+ }
177271
177954
  }
177272
- const resolved = [];
177273
- for (const target of targets) {
177274
- const code = graph.codeByEndpoint.get(target);
177275
- if (code !== undefined) {
177276
- resolved.push(code);
177277
- continue;
177955
+ markDocumented(state, symbol) {
177956
+ if (!state.undocumentedSymbols.includes(symbol)) {
177957
+ return;
177278
177958
  }
177279
- const doc = graph.docByEndpoint.get(target);
177280
- if (doc !== undefined) {
177281
- resolved.push(doc);
177959
+ state.undocumentedSymbols = state.undocumentedSymbols.filter((candidate) => candidate !== symbol);
177960
+ state.symbols = [...state.symbols, symbol];
177961
+ state.changed = true;
177962
+ }
177963
+ markAnnotated(state, endpoint) {
177964
+ const index = state.headings.findIndex((heading2) => heading2.anchor?.endpoint === endpoint);
177965
+ const heading = state.headings[index];
177966
+ if (heading === undefined || heading.hasCodeAnnotation) {
177967
+ return;
177968
+ }
177969
+ state.headings = state.headings.toSpliced(index, 1, {
177970
+ ...heading,
177971
+ hasCodeAnnotation: true
177972
+ });
177973
+ state.changed = true;
177974
+ }
177975
+ symbolNotFoundMessage(entry, state) {
177976
+ const base = `Code symbol ${entry.codeEndpoint} declared in the link manifest does not exist.`;
177977
+ const candidates = [...state.byEndpoint.values()].map((symbol) => symbol.canonicalId);
177978
+ const suggestion = nearestMatch(entry.code.fragment, candidates);
177979
+ return suggestion === undefined ? base : `${base} Did you mean \`${suggestion}\`?`;
177980
+ }
177981
+ report(code, target, message, entry, side, language) {
177982
+ const range = side === "code" ? entry.codeRange : entry.docRange;
177983
+ const diagnostic = {
177984
+ severity: "error",
177985
+ code,
177986
+ target,
177987
+ message,
177988
+ location: { ...entry.location, line: range.start.line, column: range.start.column },
177989
+ range
177990
+ };
177991
+ if (language !== undefined) {
177992
+ diagnostic.language = language;
177282
177993
  }
177994
+ this.diagnostics.push(diagnostic);
177283
177995
  }
177284
- return resolved.toSorted(compareEndpoints);
177285
- }
177286
- function addTo(map, key, value) {
177287
- const existing = map.get(key);
177288
- if (existing === undefined) {
177289
- map.set(key, new Set([value]));
177290
- return;
177996
+ result() {
177997
+ const codeFiles = this.codeOrder.map((file) => {
177998
+ const state = this.codeStates.get(file.filePath);
177999
+ if (state === undefined || !state.changed) {
178000
+ return file;
178001
+ }
178002
+ return {
178003
+ ...file,
178004
+ symbols: state.symbols,
178005
+ undocumentedSymbols: state.undocumentedSymbols,
178006
+ links: state.links
178007
+ };
178008
+ });
178009
+ const docFiles = this.docOrder.map((file) => {
178010
+ const state = this.docStates.get(file.filePath);
178011
+ if (state === undefined || !state.changed) {
178012
+ return file;
178013
+ }
178014
+ return { ...file, headings: state.headings, links: state.links };
178015
+ });
178016
+ return { codeFiles, docFiles, diagnostics: this.diagnostics };
177291
178017
  }
177292
- existing.add(value);
177293
178018
  }
177294
- function compareEndpoints(left, right) {
177295
- return compareEndpointOrder({ ...left.location, endpoint: left.endpoint }, { ...right.location, endpoint: right.endpoint });
178019
+ function pairKey(source, target) {
178020
+ return `${source} -> ${target}`;
178021
+ }
178022
+ function manifestLink(source, target, entry, side) {
178023
+ return {
178024
+ source,
178025
+ target,
178026
+ location: entry.location,
178027
+ targetRange: side === "code" ? entry.codeRange : entry.docRange
178028
+ };
177296
178029
  }
177297
178030
 
177298
178031
  // src/core/markdown-syntax.ts
@@ -177565,10 +178298,17 @@ function scanProject(options) {
177565
178298
  if (!configResult.ok) {
177566
178299
  return { ok: false, diagnostics: configResult.diagnostics };
177567
178300
  }
178301
+ const manifestResult = loadLinkManifest(options.projectRoot);
178302
+ if (!manifestResult.ok) {
178303
+ return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
178304
+ }
177568
178305
  const collectCode = options.collectCode ?? collectCodeFiles;
177569
178306
  const collectDocs = options.collectDocs ?? collectFiles;
177570
178307
  const readFile = options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath));
177571
- const diagnostics = [...configResult.diagnostics];
178308
+ const diagnostics = [
178309
+ ...configResult.diagnostics,
178310
+ ...manifestResult.diagnostics
178311
+ ];
177572
178312
  const contentByFile = options.keepContent ? new Map : undefined;
177573
178313
  const codeScan = scanCodeFiles(options.projectRoot, collectCode(options.projectRoot, configResult.config.include.code), configResult.config.include.code, readFile, contentByFile === undefined ? undefined : (relPath, content) => contentByFile.set(relPath, content));
177574
178314
  diagnostics.push(...codeScan.diagnostics);
@@ -177584,17 +178324,24 @@ function scanProject(options) {
177584
178324
  diagnostics.push(...scan2.diagnostics);
177585
178325
  docFiles.push(scan2);
177586
178326
  }
177587
- const codeFiles = codeScan.codeFiles;
178327
+ const applied = applyLinkManifest({
178328
+ manifest: manifestResult.manifest,
178329
+ codeFiles: codeScan.codeFiles,
178330
+ docFiles,
178331
+ scanDiagnostics: diagnostics
178332
+ });
178333
+ diagnostics.push(...applied.diagnostics);
178334
+ const codeFiles = applied.codeFiles;
177588
178335
  const scan = {
177589
178336
  codeFiles,
177590
- docFiles,
178337
+ docFiles: applied.docFiles,
177591
178338
  diagnostics
177592
178339
  };
177593
178340
  if (contentByFile !== undefined) {
177594
178341
  scan.contentByFile = contentByFile;
177595
178342
  }
177596
178343
  if (options.buildGraph === true) {
177597
- scan.graph = buildLinkGraph(codeFiles, docFiles);
178344
+ scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
177598
178345
  }
177599
178346
  return {
177600
178347
  ok: true,
@@ -177743,7 +178490,7 @@ function resolveLinks(input) {
177743
178490
  continue;
177744
178491
  }
177745
178492
  for (const link of file.links) {
177746
- docToCodePairs.add(pairKey(link.source, link.target));
178493
+ docToCodePairs.add(pairKey2(link.source, link.target));
177747
178494
  }
177748
178495
  }
177749
178496
  const codeToDocPairs = new Set;
@@ -177752,7 +178499,7 @@ function resolveLinks(input) {
177752
178499
  continue;
177753
178500
  }
177754
178501
  for (const link of file.links) {
177755
- codeToDocPairs.add(pairKey(link.source, link.target));
178502
+ codeToDocPairs.add(pairKey2(link.source, link.target));
177756
178503
  }
177757
178504
  }
177758
178505
  for (const file of input.codeFiles) {
@@ -177773,7 +178520,7 @@ function resolveLinks(input) {
177773
178520
  diagnostics.push(relationshipDiagnostic("doc_anchor_not_found", link.source, docEndpoint, `Doc anchor ${docEndpoint} referenced by ${link.source} does not exist.`, link));
177774
178521
  continue;
177775
178522
  }
177776
- if (!docToCodePairs.has(pairKey(docEndpoint, link.source))) {
178523
+ if (!docToCodePairs.has(pairKey2(docEndpoint, link.source))) {
177777
178524
  diagnostics.push(relationshipDiagnostic("doc_backlink_not_found", link.source, docEndpoint, `Doc anchor ${docEndpoint} has no matching @code backlink to ${link.source}.`, link));
177778
178525
  }
177779
178526
  }
@@ -177792,7 +178539,7 @@ function resolveLinks(input) {
177792
178539
  diagnostics.push(relationshipDiagnostic("code_file_not_found", link.source, codeEndpoint, `Code file ${codeFilePath} referenced by ${link.source} is not in the managed code set.`, link));
177793
178540
  continue;
177794
178541
  }
177795
- if (!codeToDocPairs.has(pairKey(codeEndpoint, link.source))) {
178542
+ if (!codeToDocPairs.has(pairKey2(codeEndpoint, link.source))) {
177796
178543
  diagnostics.push(relationshipDiagnostic("code_backlink_not_found", link.source, codeEndpoint, `Code endpoint ${codeEndpoint} has no matching @doc pair back to ${link.source}.`, link));
177797
178544
  }
177798
178545
  }
@@ -177827,6 +178574,9 @@ function auditUndocumentedSymbols(input, erroredFiles) {
177827
178574
  continue;
177828
178575
  }
177829
178576
  for (const symbol of file.undocumentedSymbols) {
178577
+ if (symbol.isMember === true) {
178578
+ continue;
178579
+ }
177830
178580
  diagnostics.push({
177831
178581
  severity: "warning",
177832
178582
  code: "undocumented_symbol",
@@ -177898,19 +178648,7 @@ function unlinkedDocSectionDiagnostic(node, anchor) {
177898
178648
  }
177899
178649
  return diagnostic;
177900
178650
  }
177901
- function collectErroredFiles(diagnostics) {
177902
- const errored = new Set;
177903
- for (const diagnostic of diagnostics) {
177904
- if (diagnostic.code === "file_read_error" || diagnostic.code === "code_parse_error" || isFileScopedScannerDiagnostic(diagnostic)) {
177905
- errored.add(diagnostic.target);
177906
- }
177907
- }
177908
- return errored;
177909
- }
177910
- function isFileScopedScannerDiagnostic(diagnostic) {
177911
- return (diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed") && diagnostic.language !== undefined && diagnostic.target !== diagnostic.language;
177912
- }
177913
- function pairKey(source, target) {
178651
+ function pairKey2(source, target) {
177914
178652
  return `${source}->${target}`;
177915
178653
  }
177916
178654
  function relationshipDiagnostic(code, source, target, message, link) {
@@ -178498,8 +179236,33 @@ function comparePairs(left, right) {
178498
179236
  return left.docEndpoint.localeCompare(right.docEndpoint) || left.codeEndpoint.localeCompare(right.codeEndpoint);
178499
179237
  }
178500
179238
 
179239
+ // src/core/package-root.ts
179240
+ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "node:fs";
179241
+ import { dirname as dirname3, join as join6 } from "node:path";
179242
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
179243
+ function resolvePackageRoot(moduleUrl = import.meta.url) {
179244
+ const modulePath = fileURLToPath2(moduleUrl);
179245
+ let resolved;
179246
+ try {
179247
+ resolved = realpathSync2(modulePath);
179248
+ } catch {
179249
+ resolved = modulePath;
179250
+ }
179251
+ let dir = dirname3(resolved);
179252
+ for (;; ) {
179253
+ if (existsSync2(join6(dir, "templates", "skills"))) {
179254
+ return dir;
179255
+ }
179256
+ const parent = dirname3(dir);
179257
+ if (parent === dir) {
179258
+ return dir;
179259
+ }
179260
+ dir = parent;
179261
+ }
179262
+ }
179263
+
178501
179264
  // src/lsp/server.ts
178502
- import { fileURLToPath as fileURLToPath3 } from "node:url";
179265
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
178503
179266
 
178504
179267
  // src/lsp/position.ts
178505
179268
  function toLspPosition(position) {
@@ -178677,11 +179440,11 @@ function counterpartLocators(state, filePath, position) {
178677
179440
 
178678
179441
  // src/lsp/paths.ts
178679
179442
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "node:path";
178680
- import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
179443
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
178681
179444
  function uriToRelativePath(projectRoot, uri) {
178682
179445
  let absolute;
178683
179446
  try {
178684
- absolute = fileURLToPath2(uri);
179447
+ absolute = fileURLToPath3(uri);
178685
179448
  } catch {
178686
179449
  return;
178687
179450
  }
@@ -179104,7 +179867,7 @@ function runLspServer() {
179104
179867
  }
179105
179868
  function uriToPath(uri) {
179106
179869
  try {
179107
- return fileURLToPath3(uri);
179870
+ return fileURLToPath4(uri);
179108
179871
  } catch {
179109
179872
  return;
179110
179873
  }
@@ -179138,363 +179901,264 @@ function fullChangeText(changes) {
179138
179901
  return typeof last.text === "string" ? last.text : undefined;
179139
179902
  }
179140
179903
 
179141
- // src/cli/docs.ts
179142
- import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
179143
- import { join as join6 } from "node:path";
179144
-
179145
- // src/core/init-plan.ts
179146
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
179147
- import { dirname as dirname3, join as join5, relative as relative3 } from "node:path";
179148
- import { fileURLToPath as fileURLToPath4 } from "node:url";
179149
- var CONFIG_FILE_NAME2 = "docbridge.config.json";
179150
- var INIT_SKILL_NAMES = [
179151
- "docbridge-adopt",
179152
- "docbridge-annotate",
179153
- "docbridge-link",
179154
- "docbridge-review",
179155
- "docbridge-sync"
179156
- ];
179157
- var INIT_WITH_AGENT_SKILL_NAMES = ["docbridge-adopt"];
179158
- function planInitCommand(input) {
179159
- const packageRoot = input.packageRoot ?? resolvePackageRoot();
179160
- const existingConfig = readExistingConfig(input.projectRoot);
179161
- const messages = [];
179162
- const nextSteps = [];
179163
- let exitCode = 0;
179164
- const agentResolution = input.command === "init" ? resolveInitAgentTarget(input.discovery, input.options) : resolveInitWithAgentTarget(input.discovery, input.options);
179165
- if (agentResolution.error !== undefined) {
179166
- return {
179167
- command: input.command,
179168
- configOps: [],
179169
- skillOps: [],
179170
- messages: [agentResolution.error],
179171
- nextSteps: [],
179172
- agentGuidance: [],
179173
- exitCode: 1
179174
- };
179175
- }
179176
- const agentTarget = agentResolution.target ?? "none";
179177
- const configOps = input.command === "init" ? planConfigOperations({
179178
- projectRoot: input.projectRoot,
179179
- options: input.options,
179180
- discovery: input.discovery,
179181
- confirmedScope: input.confirmedScope,
179182
- existingConfig,
179183
- messages
179184
- }) : [];
179185
- if (configOps.some((operation) => operation.action === "create" || operation.action === "would-create")) {
179186
- nextSteps.push("Review docbridge.config.json and adjust include.docs or include.code if needed.");
179187
- nextSteps.push("Add @doc and @code annotations to link code and documentation.");
179188
- nextSteps.push("Run docbridge check when links exist.");
179189
- } else if (existingConfig.ok) {
179190
- messages.push("Existing docbridge.config.json was left unchanged.");
179191
- summarizeExistingConfig(existingConfig.config, messages);
179192
- } else if (existingConfig.rawText !== undefined) {
179193
- messages.push("Existing docbridge.config.json is invalid and was not modified.");
179194
- for (const diagnostic of existingConfig.diagnostics) {
179195
- messages.push(diagnostic.message);
179196
- }
179197
- messages.push("Repair the config manually or delete it before re-running init.");
179198
- exitCode = 1;
179199
- }
179200
- const skillOps = planSkillOperations({
179201
- command: input.command,
179202
- projectRoot: input.projectRoot,
179203
- packageRoot,
179204
- agentTarget,
179205
- options: input.options
179206
- });
179207
- const agentGuidance = input.command === "init-with-agent" && agentTarget !== "none" ? buildAgentGuidance(input.projectRoot, agentTarget) : [];
179208
- if (input.command === "init-with-agent") {
179209
- nextSteps.push("Run the printed one-shot command or fallback prompt in your agent.");
179210
- nextSteps.push("Let docbridge-adopt confirm scope and create or improve docbridge.config.json.");
179211
- }
179212
- if (input.options.yes && input.command === "init" && configOps.length === 0 && existingConfig.rawText === undefined && (input.discovery.docs.ambiguous || input.discovery.code.ambiguous)) {
179213
- exitCode = 1;
179214
- }
179215
- return {
179216
- command: input.command,
179217
- configOps,
179218
- skillOps,
179219
- messages,
179220
- nextSteps,
179221
- agentGuidance,
179222
- exitCode
179223
- };
179224
- }
179225
- function buildConfigFromScope(scope) {
179226
- const code = {};
179227
- for (const language of scope.languages) {
179228
- code[language.language] = { patterns: [...language.patterns] };
179229
- }
179230
- return {
179231
- include: {
179232
- code,
179233
- docs: [scope.docsPattern]
179234
- }
179235
- };
179904
+ // src/setup/update-cache.ts
179905
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
179906
+ import { homedir } from "node:os";
179907
+ import { dirname as dirname4, join as join7 } from "node:path";
179908
+ var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
179909
+ var UPDATE_CACHE_SCHEMA_VERSION = 1;
179910
+ function resolveUpdateCachePath(env = process.env, home = homedir()) {
179911
+ const explicit = env.DOCBRIDGE_UPDATE_CACHE;
179912
+ if (explicit !== undefined && explicit.length > 0) {
179913
+ return explicit;
179914
+ }
179915
+ const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join7(home, ".cache");
179916
+ return join7(cacheRoot, "docbridge", "update-check.json");
179236
179917
  }
179237
- function resolvePackageRoot(moduleUrl = import.meta.url) {
179238
- const modulePath = fileURLToPath4(moduleUrl);
179239
- let resolved;
179918
+ function readUpdateCache(path) {
179919
+ let rawText;
179240
179920
  try {
179241
- resolved = realpathSync2(modulePath);
179921
+ rawText = readFileSync4(path, "utf8");
179242
179922
  } catch {
179243
- resolved = modulePath;
179244
- }
179245
- let dir = dirname3(resolved);
179246
- for (;; ) {
179247
- if (existsSync2(join5(dir, "templates", "skills"))) {
179248
- return dir;
179249
- }
179250
- const parent = dirname3(dir);
179251
- if (parent === dir) {
179252
- return dir;
179253
- }
179254
- dir = parent;
179923
+ return;
179255
179924
  }
179256
- }
179257
- function listDistributableSkills(packageRoot) {
179258
- const skillsRoot = join5(packageRoot, "templates", "skills");
179259
- let entries;
179925
+ let parsed;
179260
179926
  try {
179261
- entries = readdirSync2(skillsRoot);
179927
+ parsed = JSON.parse(rawText);
179262
179928
  } catch {
179263
- return [];
179929
+ return;
179264
179930
  }
179265
- return entries.filter((entry) => entry.startsWith("docbridge-")).filter((entry) => existsSync2(join5(skillsRoot, entry, "SKILL.md"))).toSorted();
179931
+ return toUpdateCacheRecord(parsed);
179266
179932
  }
179267
- function formatInitPlan(plan) {
179268
- const lines = [];
179269
- for (const message of plan.messages) {
179270
- lines.push(message);
179271
- }
179272
- if (plan.configOps.length > 0) {
179273
- lines.push("");
179274
- lines.push("Config:");
179275
- for (const operation of plan.configOps) {
179276
- lines.push(formatFileOp(operation));
179277
- if (operation.content !== undefined) {
179278
- lines.push(operation.content);
179279
- }
179280
- }
179281
- }
179282
- if (plan.skillOps.length > 0) {
179283
- lines.push("");
179284
- lines.push("Skills:");
179285
- for (const operation of plan.skillOps) {
179286
- lines.push(formatFileOp(operation));
179287
- }
179288
- }
179289
- if (plan.agentGuidance.length > 0) {
179290
- lines.push("");
179291
- lines.push("Agent setup:");
179292
- for (const guidance of plan.agentGuidance) {
179293
- lines.push("");
179294
- lines.push(`${capitalize(guidance.agent)} destination: ${guidance.destination}`);
179295
- lines.push(`One-shot command: ${guidance.oneShotCommand}`);
179296
- lines.push("Fallback prompt:");
179297
- lines.push(guidance.fallbackPrompt);
179298
- }
179299
- }
179300
- if (plan.nextSteps.length > 0) {
179301
- lines.push("");
179302
- lines.push("Next steps:");
179303
- for (const step of plan.nextSteps) {
179304
- lines.push(`- ${step}`);
179305
- }
179306
- }
179307
- return `${lines.join(`
179308
- `)}
179309
- `;
179933
+ function writeUpdateCache(path, record) {
179934
+ try {
179935
+ mkdirSync2(dirname4(path), { recursive: true });
179936
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}
179937
+ `, "utf8");
179938
+ } catch {}
179310
179939
  }
179311
- function planConfigOperations(input) {
179312
- if (input.existingConfig.rawText !== undefined) {
179313
- return [];
179314
- }
179315
- const scope = input.confirmedScope ?? inferConfirmedScope(input.discovery, input.options);
179316
- if (scope === undefined) {
179317
- if (input.discovery.docs.message !== undefined) {
179318
- input.messages.push(input.discovery.docs.message);
179319
- }
179320
- if (input.discovery.code.message !== undefined) {
179321
- input.messages.push(input.discovery.code.message);
179322
- }
179323
- if (input.options.yes) {
179324
- input.messages.push("Automatic setup stopped because docs or code scope is ambiguous. Re-run interactively or confirm scope explicitly.");
179325
- }
179326
- return [];
179327
- }
179328
- const config = buildConfigFromScope(scope);
179329
- const content = `${JSON.stringify({ $schema: "./schemas/docbridge.schema.json", ...config }, null, 2)}
179330
- `;
179331
- const action = input.options.dryRun ? "would-create" : "create";
179332
- input.messages.push(`Docs scope: ${scope.docsPattern}`);
179333
- input.messages.push(`Code scope: ${scope.languages.map((language) => `${language.language} (${language.patterns.join(", ")})`).join("; ")}`);
179334
- return [
179335
- {
179336
- action,
179337
- path: CONFIG_FILE_NAME2,
179338
- content,
179339
- reason: "Create docbridge.config.json from discovered scope."
179340
- }
179341
- ];
179940
+ function isUpdateCacheFresh(record, now, ttlMs = UPDATE_CACHE_TTL_MS) {
179941
+ const age = now - record.checkedAt;
179942
+ return age >= 0 && age < ttlMs;
179342
179943
  }
179343
- function inferConfirmedScope(discovery, options) {
179344
- if (discovery.docs.ambiguous || discovery.code.ambiguous) {
179944
+ function toUpdateCacheRecord(value) {
179945
+ if (typeof value !== "object" || value === null) {
179345
179946
  return;
179346
179947
  }
179347
- if (discovery.docs.recommended === undefined || discovery.code.languages.length === 0) {
179948
+ const candidate = value;
179949
+ if (candidate.schema !== UPDATE_CACHE_SCHEMA_VERSION) {
179348
179950
  return;
179349
179951
  }
179350
- if (!options.yes) {
179952
+ if (typeof candidate.checkedAt !== "number" || !Number.isFinite(candidate.checkedAt)) {
179953
+ return;
179954
+ }
179955
+ if (candidate.latest !== null && typeof candidate.latest !== "string") {
179351
179956
  return;
179352
179957
  }
179353
179958
  return {
179354
- docsPattern: discovery.docs.recommended.pattern,
179355
- languages: discovery.code.languages
179959
+ schema: UPDATE_CACHE_SCHEMA_VERSION,
179960
+ checkedAt: candidate.checkedAt,
179961
+ latest: candidate.latest
179356
179962
  };
179357
179963
  }
179358
- function planSkillOperations(input) {
179359
- if (input.agentTarget === "none") {
179360
- return [];
179964
+
179965
+ // src/setup/version.ts
179966
+ var STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
179967
+ function parseStableVersion(value) {
179968
+ const match = STABLE_VERSION_PATTERN.exec(value.trim());
179969
+ if (match === null) {
179970
+ return;
179361
179971
  }
179362
- const skillNames = input.command === "init" ? [...INIT_SKILL_NAMES] : [...INIT_WITH_AGENT_SKILL_NAMES];
179363
- const destinations = agentDestinations(input.agentTarget);
179364
- const availableSkills = new Set(listDistributableSkills(input.packageRoot));
179365
- const operations = [];
179366
- for (const destination of destinations) {
179367
- for (const skillName of skillNames) {
179368
- if (!availableSkills.has(skillName)) {
179369
- continue;
179370
- }
179371
- const destinationDir = join5(input.projectRoot, destination, skillName);
179372
- const exists = existsSync2(destinationDir);
179373
- let action;
179374
- let reason;
179375
- if (exists && input.options.force) {
179376
- action = input.options.dryRun ? "would-overwrite" : "overwrite";
179377
- reason = "Overwrite existing skill directory.";
179378
- } else if (exists) {
179379
- action = "skip";
179380
- reason = "Skill directory already exists.";
179381
- } else {
179382
- action = input.options.dryRun ? "would-create" : "create";
179383
- reason = "Install distributable skill template.";
179384
- }
179385
- operations.push({
179386
- action,
179387
- path: relative3(input.projectRoot, destinationDir),
179388
- reason
179389
- });
179390
- }
179972
+ const [, major, minor, patch] = match;
179973
+ if (major === undefined || minor === undefined || patch === undefined) {
179974
+ return;
179391
179975
  }
179392
- return operations;
179976
+ return {
179977
+ major: Number.parseInt(major, 10),
179978
+ minor: Number.parseInt(minor, 10),
179979
+ patch: Number.parseInt(patch, 10)
179980
+ };
179393
179981
  }
179394
- function buildAgentGuidance(projectRoot, agentTarget) {
179395
- const guidance = [];
179396
- if (agentTarget === "codex" || agentTarget === "both") {
179397
- guidance.push({
179398
- agent: "codex",
179399
- destination: ".agents/skills/docbridge-adopt/",
179400
- oneShotCommand: `cd ${projectRoot} && $docbridge_adopt_prompt`,
179401
- fallbackPrompt: buildFallbackPrompt(projectRoot, "codex")
179402
- });
179403
- }
179404
- if (agentTarget === "claude" || agentTarget === "both") {
179405
- guidance.push({
179406
- agent: "claude",
179407
- destination: ".claude/skills/docbridge-adopt/",
179408
- oneShotCommand: `cd ${projectRoot} && /docbridge-adopt`,
179409
- fallbackPrompt: buildFallbackPrompt(projectRoot, "claude")
179410
- });
179411
- }
179412
- for (const entry of guidance) {
179413
- entry.oneShotCommand = entry.oneShotCommand.replace("$docbridge_adopt_prompt", 'codex --prompt "Use the docbridge-adopt skill to adopt DocBridge in this repository."');
179414
- }
179415
- return guidance;
179982
+ function compareStableVersions(left, right) {
179983
+ return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
179416
179984
  }
179417
- function buildFallbackPrompt(projectRoot, agent) {
179418
- const invocation = agent === "claude" ? "Use the docbridge-adopt skill" : "Use the docbridge-adopt skill from .agents/skills/docbridge-adopt";
179419
- return `${invocation} to adopt DocBridge in ${projectRoot}. Confirm docs and code scope, create or improve docbridge.config.json, install the companion DocBridge skills, and suggest the next linking steps.`;
179985
+ function isNewerStableVersion(candidate, current) {
179986
+ const parsedCandidate = parseStableVersion(candidate);
179987
+ const parsedCurrent = parseStableVersion(current);
179988
+ if (parsedCandidate === undefined || parsedCurrent === undefined) {
179989
+ return false;
179990
+ }
179991
+ return compareStableVersions(parsedCandidate, parsedCurrent) > 0;
179420
179992
  }
179421
- function resolveInitAgentTarget(discovery, options) {
179422
- if (options.agentTarget !== undefined) {
179423
- return { target: options.agentTarget, error: undefined };
179993
+
179994
+ // src/setup/registry.ts
179995
+ var NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/docbridge/latest";
179996
+ var REGISTRY_TIMEOUT_MS = 1500;
179997
+ function selectStableVersion(payload) {
179998
+ if (typeof payload !== "object" || payload === null) {
179999
+ return;
179424
180000
  }
179425
- if (options.yes) {
179426
- return { target: discovery.agent.defaultTarget, error: undefined };
180001
+ const version = payload.version;
180002
+ if (typeof version !== "string") {
180003
+ return;
179427
180004
  }
179428
- return { target: discovery.agent.recommendedTarget, error: undefined };
180005
+ return parseStableVersion(version) === undefined ? undefined : version.trim();
179429
180006
  }
179430
- function resolveInitWithAgentTarget(discovery, options) {
179431
- if (options.agentTarget !== undefined) {
179432
- if (options.agentTarget === "none") {
179433
- return {
179434
- target: undefined,
179435
- error: "init-with-agent requires an agent target other than none."
179436
- };
180007
+ function resolveRegistryUrl(env = process.env) {
180008
+ const override = env.DOCBRIDGE_REGISTRY_URL;
180009
+ return override !== undefined && override.length > 0 ? override : NPM_REGISTRY_LATEST_URL;
180010
+ }
180011
+ async function fetchLatestStableVersion(options = {}) {
180012
+ const fetchImpl = options.fetchImpl ?? fetch;
180013
+ const url = options.url ?? resolveRegistryUrl();
180014
+ try {
180015
+ const response = await fetchImpl(url, {
180016
+ signal: AbortSignal.timeout(options.timeoutMs ?? REGISTRY_TIMEOUT_MS),
180017
+ headers: { accept: "application/json" }
180018
+ });
180019
+ if (!response.ok) {
180020
+ return;
179437
180021
  }
179438
- return { target: options.agentTarget, error: undefined };
180022
+ return selectStableVersion(await response.json());
180023
+ } catch {
180024
+ return;
179439
180025
  }
179440
- if (discovery.agent.defaultTarget === "none") {
179441
- if (options.yes) {
179442
- return {
179443
- target: undefined,
179444
- error: "init-with-agent --yes requires an explicit agent target when no .agents/ or .claude/ directory exists. Pass --agent-target codex, claude, or both."
179445
- };
179446
- }
179447
- return { target: discovery.agent.recommendedTarget, error: undefined };
180026
+ }
180027
+ async function resolveLatestStableVersion(options = {}) {
180028
+ const now = options.now ?? Date.now();
180029
+ const cachePath = options.cachePath ?? resolveUpdateCachePath();
180030
+ if (options.forceRefresh !== true) {
180031
+ const cached = readUpdateCache(cachePath);
180032
+ if (cached !== undefined && isUpdateCacheFresh(cached, now)) {
180033
+ return cached.latest === null ? { status: "unavailable", source: "cache" } : { status: "ok", latest: cached.latest, source: "cache" };
180034
+ }
180035
+ }
180036
+ const fetched = await fetchLatestStableVersion({
180037
+ ...options.url !== undefined ? { url: options.url } : {},
180038
+ ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {},
180039
+ ...options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}
180040
+ });
180041
+ writeUpdateCache(cachePath, {
180042
+ schema: UPDATE_CACHE_SCHEMA_VERSION,
180043
+ checkedAt: now,
180044
+ latest: fetched ?? null
180045
+ });
180046
+ return fetched === undefined ? { status: "unavailable", source: "network" } : { status: "ok", latest: fetched, source: "network" };
180047
+ }
180048
+
180049
+ // src/setup/upgrade-guidance.ts
180050
+ import { sep as sep2 } from "node:path";
180051
+ var PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
180052
+ var PROJECT_COMMANDS = {
180053
+ bun: "bun add -d docbridge@latest",
180054
+ npm: "npm install --save-dev docbridge@latest",
180055
+ pnpm: "pnpm add -D docbridge@latest",
180056
+ yarn: "yarn add -D docbridge@latest"
180057
+ };
180058
+ var GLOBAL_COMMANDS = {
180059
+ bun: "bun add -g docbridge@latest",
180060
+ npm: "npm install -g docbridge@latest",
180061
+ pnpm: "pnpm add -g docbridge@latest",
180062
+ yarn: "yarn global add docbridge@latest"
180063
+ };
180064
+ function detectPackageManager(env = process.env) {
180065
+ const userAgent = env.npm_config_user_agent;
180066
+ if (userAgent === undefined || userAgent.length === 0) {
180067
+ return;
179448
180068
  }
179449
- return { target: discovery.agent.defaultTarget, error: undefined };
180069
+ const name = userAgent.trim().split("/", 1)[0]?.toLowerCase();
180070
+ return PACKAGE_MANAGERS.find((manager) => manager === name);
179450
180071
  }
179451
- function readExistingConfig(projectRoot) {
179452
- const configPath = join5(projectRoot, CONFIG_FILE_NAME2);
179453
- let rawText;
179454
- try {
179455
- rawText = readFileSync3(configPath, "utf8");
179456
- } catch {
179457
- rawText = undefined;
180072
+ function detectInstallScope(packageRoot, candidateRoots) {
180073
+ const installBase = resolveInstallBase(packageRoot);
180074
+ if (installBase === undefined) {
180075
+ return "unknown";
179458
180076
  }
179459
- const resolved = resolveConfig(rawText);
180077
+ return candidateRoots.some((root) => isSameOrAncestor(installBase, root)) ? "project" : "global";
180078
+ }
180079
+ function detectUpgradeGuidance(input) {
180080
+ const packageManager = detectPackageManager(input.env ?? process.env);
180081
+ const scope = detectInstallScope(input.packageRoot, [
180082
+ input.projectRoot,
180083
+ input.currentDirectory ?? process.cwd()
180084
+ ]);
180085
+ const commands = scope === "global" ? GLOBAL_COMMANDS : PROJECT_COMMANDS;
180086
+ const primaryManager = packageManager ?? "npm";
179460
180087
  return {
179461
- rawText,
179462
- ok: resolved.ok,
179463
- config: resolved.config,
179464
- diagnostics: resolved.diagnostics
180088
+ packageManager,
180089
+ scope,
180090
+ primaryCommand: commands[primaryManager],
180091
+ alternativeCommands: PACKAGE_MANAGERS.filter((manager) => manager !== primaryManager).map((manager) => commands[manager])
179465
180092
  };
179466
180093
  }
179467
- function summarizeExistingConfig(config, messages) {
179468
- messages.push(`Docs scope: ${config.include.docs.join(", ")}`);
179469
- const codeSummary = Object.entries(config.include.code).map(([language, entry]) => `${language}: ${entry?.patterns.join(", ") ?? ""}`).join("; ");
179470
- messages.push(`Code scope: ${codeSummary}`);
180094
+ function formatUpgradeGuidance(guidance) {
180095
+ const scopeLabel = guidance.scope === "unknown" ? "install scope not detected" : `${guidance.scope} install`;
180096
+ return [
180097
+ `Upgrade command (${scopeLabel}): ${guidance.primaryCommand}`,
180098
+ `Other package managers: ${guidance.alternativeCommands.join(", ")}`
180099
+ ];
179471
180100
  }
179472
- function agentDestinations(agentTarget) {
179473
- switch (agentTarget) {
179474
- case "codex":
179475
- return [".agents/skills"];
179476
- case "claude":
179477
- return [".claude/skills"];
179478
- case "both":
179479
- return [".agents/skills", ".claude/skills"];
179480
- default:
179481
- return [];
180101
+ function resolveInstallBase(packageRoot) {
180102
+ const marker = `${sep2}node_modules${sep2}`;
180103
+ const markerIndex = withTrailingSeparator(packageRoot).indexOf(marker);
180104
+ return markerIndex === -1 ? undefined : packageRoot.slice(0, markerIndex);
180105
+ }
180106
+ function isSameOrAncestor(candidate, descendant) {
180107
+ return withTrailingSeparator(descendant).startsWith(withTrailingSeparator(candidate));
180108
+ }
180109
+ function withTrailingSeparator(path) {
180110
+ return path.endsWith(sep2) ? path : `${path}${sep2}`;
180111
+ }
180112
+
180113
+ // src/setup/update-notice.ts
180114
+ var SUPPRESSED_COMMANDS = new Set(["lsp", "upgrade"]);
180115
+ function isUpdateCheckOptedOut(env = process.env) {
180116
+ return isEnabledFlag(env.DOCBRIDGE_NO_UPDATE_CHECK);
180117
+ }
180118
+ function decideUpdateCheck(input) {
180119
+ const env = input.env ?? process.env;
180120
+ if (isUpdateCheckOptedOut(env)) {
180121
+ return { enabled: false, reason: "opt-out" };
180122
+ }
180123
+ if (isEnabledFlag(env.CI)) {
180124
+ return { enabled: false, reason: "ci" };
179482
180125
  }
180126
+ if (!input.isTty) {
180127
+ return { enabled: false, reason: "non-tty" };
180128
+ }
180129
+ if (input.argv.includes("--json")) {
180130
+ return { enabled: false, reason: "json-output" };
180131
+ }
180132
+ const command = input.argv[0];
180133
+ if (command !== undefined && SUPPRESSED_COMMANDS.has(command)) {
180134
+ return { enabled: false, reason: "machine-command" };
180135
+ }
180136
+ return { enabled: true };
179483
180137
  }
179484
- function formatFileOp(operation) {
179485
- let label = operation.action;
179486
- if (operation.action === "would-create") {
179487
- label = "would create";
179488
- } else if (operation.action === "would-overwrite") {
179489
- label = "would overwrite";
180138
+ function formatUpdateNotice(input) {
180139
+ if (!isNewerStableVersion(input.latest, input.current)) {
180140
+ return;
179490
180141
  }
179491
- const reason = operation.reason ? ` (${operation.reason})` : "";
179492
- return `- ${label} ${operation.path}${reason}`;
180142
+ return [
180143
+ `Update available: docbridge ${input.current} -> ${input.latest}`,
180144
+ formatUpgradeGuidance(input.guidance)[0] ?? "",
180145
+ "Run `docbridge upgrade --check` for details, or set DOCBRIDGE_NO_UPDATE_CHECK=1 to silence this notice.",
180146
+ ""
180147
+ ].join(`
180148
+ `);
179493
180149
  }
179494
- function capitalize(value) {
179495
- return value.charAt(0).toUpperCase() + value.slice(1);
180150
+ function isEnabledFlag(value) {
180151
+ if (value === undefined) {
180152
+ return false;
180153
+ }
180154
+ const normalized = value.trim().toLowerCase();
180155
+ return normalized.length > 0 && normalized !== "0" && normalized !== "false";
179496
180156
  }
179497
180157
 
180158
+ // src/cli/docs.ts
180159
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
180160
+ import { join as join8 } from "node:path";
180161
+
179498
180162
  // src/cli/errors.ts
179499
180163
  class CliError extends Error {
179500
180164
  guidance;
@@ -179555,6 +180219,19 @@ function agentTargetGuidance(command) {
179555
180219
  ].join(`
179556
180220
  `);
179557
180221
  }
180222
+ function upgradeConfirmationGuidance() {
180223
+ return [
180224
+ "Confirm the migration explicitly:",
180225
+ "",
180226
+ " docbridge upgrade --force --yes",
180227
+ "",
180228
+ "Or inspect it first:",
180229
+ "",
180230
+ " docbridge upgrade --check",
180231
+ " docbridge upgrade --force --dry-run"
180232
+ ].join(`
180233
+ `);
180234
+ }
179558
180235
  function includeContentGuidance() {
179559
180236
  return [
179560
180237
  "Use --include-content together with --json:",
@@ -179581,6 +180258,9 @@ function configSetupGuidance() {
179581
180258
  function configRepairGuidance() {
179582
180259
  return "Repair or delete docbridge.config.json, then re-run `docbridge check`.";
179583
180260
  }
180261
+ function manifestRepairGuidance() {
180262
+ return "Repair or delete docbridge.links.json, then re-run `docbridge check`.";
180263
+ }
179584
180264
  function formatCliError(error) {
179585
180265
  if (!(error instanceof CliError)) {
179586
180266
  return error instanceof Error ? error.message : String(error);
@@ -179648,11 +180328,11 @@ function parseDocument(name, source) {
179648
180328
  };
179649
180329
  }
179650
180330
  function createFileDocumentationReader(packageRoot) {
179651
- const docsRoot = join6(packageRoot, "docs", "user");
180331
+ const docsRoot = join8(packageRoot, "docs", "user");
179652
180332
  function documents() {
179653
180333
  let fileNames;
179654
180334
  try {
179655
- fileNames = readdirSync3(docsRoot).filter((fileName) => fileName.endsWith(".md")).toSorted();
180335
+ fileNames = readdirSync2(docsRoot).filter((fileName) => fileName.endsWith(".md")).toSorted();
179656
180336
  } catch {
179657
180337
  throw documentationUnavailableError();
179658
180338
  }
@@ -179661,7 +180341,7 @@ function createFileDocumentationReader(packageRoot) {
179661
180341
  }
179662
180342
  return fileNames.map((fileName) => {
179663
180343
  const name = fileName.slice(0, -".md".length);
179664
- return parseDocument(name, readFileSync4(join6(docsRoot, fileName), "utf8"));
180344
+ return parseDocument(name, readFileSync5(join8(docsRoot, fileName), "utf8"));
179665
180345
  });
179666
180346
  }
179667
180347
  return {
@@ -179732,6 +180412,7 @@ var SUBCOMMANDS = [
179732
180412
  "docs",
179733
180413
  "init",
179734
180414
  "init-with-agent",
180415
+ "upgrade",
179735
180416
  "lsp"
179736
180417
  ];
179737
180418
  var ROOT_OPTION = {
@@ -179857,11 +180538,47 @@ var COMMAND_DOCS = {
179857
180538
  `),
179858
180539
  options: [JSON_OPTION]
179859
180540
  },
180541
+ upgrade: {
180542
+ usage: "[options]",
180543
+ summary: "Use to check for a newer release and migrate managed skills.",
180544
+ description: [
180545
+ "Report DocBridge version drift and reconcile the managed agent skill.",
180546
+ "Use it after a DocBridge release, or when a project still carries assets",
180547
+ "from an older layout. `--check` is read-only. Applying changes touches",
180548
+ "only the managed docbridge skill directory and the known legacy skill",
180549
+ "directories; it never edits config, hooks, CI recipes, or user code, and",
180550
+ "it never runs a package manager for you."
180551
+ ].join(`
180552
+ `),
180553
+ options: [
180554
+ {
180555
+ flag: "--root <path>",
180556
+ description: "Project root to inspect. Defaults to current directory."
180557
+ },
180558
+ {
180559
+ flag: "--check",
180560
+ description: "Report version and managed asset state without writing files."
180561
+ },
180562
+ {
180563
+ flag: "--dry-run",
180564
+ description: "Print the operations that would be applied without writing files."
180565
+ },
180566
+ { flag: "--yes", description: "Confirm destructive operations without prompting." },
180567
+ {
180568
+ flag: "--force",
180569
+ description: "Replace the managed skill and remove known legacy skill directories."
180570
+ },
180571
+ {
180572
+ flag: "--agent-target <target>",
180573
+ description: "Agent target: codex, claude, both, or none."
180574
+ }
180575
+ ]
180576
+ },
179860
180577
  init: {
179861
180578
  usage: "[options]",
179862
- summary: "Use once per project to set up config and agent skills.",
180579
+ summary: "Use once per project to set up config and the agent skill.",
179863
180580
  description: [
179864
- "Create docbridge.config.json and install the DocBridge agent skills.",
180581
+ "Create docbridge.config.json and install the DocBridge agent skill.",
179865
180582
  "Use it once per project, when adopting DocBridge. Run it with --dry-run",
179866
180583
  "first to see what would be written."
179867
180584
  ].join(`
@@ -179878,7 +180595,7 @@ var COMMAND_DOCS = {
179878
180595
  usage: "[options]",
179879
180596
  summary: "Use to let a coding agent decide the setup instead of `init`.",
179880
180597
  description: [
179881
- "Install the docbridge-adopt skill and print the commands that let a coding",
180598
+ "Install the docbridge skill and print the commands that let a coding",
179882
180599
  "agent finish the setup.",
179883
180600
  "Use it instead of `init` when an agent should decide the configuration",
179884
180601
  "rather than accepting the defaults."
@@ -179924,7 +180641,7 @@ function commandList() {
179924
180641
  return SUBCOMMANDS.map((command) => ` ${command.padEnd(width)}${COMMAND_DOCS[command].summary}`).join(`
179925
180642
  `);
179926
180643
  }
179927
- var GLOBAL_OPTION_SECTIONS = ["check", "related", "context", "graph", "docs", "init"].map((command) => {
180644
+ var GLOBAL_OPTION_SECTIONS = ["check", "related", "context", "graph", "docs", "init", "upgrade"].map((command) => {
179928
180645
  const label = `${command.charAt(0).toUpperCase()}${command.slice(1)}`;
179929
180646
  return `${label} options:
179930
180647
  ${renderOptions(COMMAND_DOCS[command].options)}`;
@@ -180021,12 +180738,12 @@ function hasHelpFlag(args) {
180021
180738
  }
180022
180739
 
180023
180740
  // src/cli/init.ts
180024
- import { cpSync, mkdirSync as mkdirSync2, readFileSync as readFileSync5, statSync as statSync3, writeFileSync } from "node:fs";
180025
- import { join as join8, resolve as resolve3 } from "node:path";
180741
+ import { readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
180742
+ import { join as join12, resolve as resolve3 } from "node:path";
180026
180743
 
180027
- // src/core/init-discovery.ts
180028
- import { existsSync as existsSync3, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
180029
- import { basename, dirname as dirname4, join as join7 } from "node:path";
180744
+ // src/setup/init-discovery.ts
180745
+ import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
180746
+ import { basename, dirname as dirname5, join as join9 } from "node:path";
180030
180747
  var HIGH_CONFIDENCE_DIR_NAMES = new Set([
180031
180748
  "docs/specs",
180032
180749
  "specs",
@@ -180091,7 +180808,7 @@ function discoverDocsScope(projectRoot) {
180091
180808
  }
180092
180809
  const grouped = new Map;
180093
180810
  for (const filePath of eligible) {
180094
- const directory = dirname4(filePath);
180811
+ const directory = dirname5(filePath);
180095
180812
  const existing = grouped.get(directory) ?? [];
180096
180813
  existing.push(filePath);
180097
180814
  grouped.set(directory, existing);
@@ -180153,8 +180870,8 @@ function discoverCodeScope(projectRoot) {
180153
180870
  };
180154
180871
  }
180155
180872
  function discoverAgentTarget(projectRoot) {
180156
- const hasAgentsDir = existsSync3(join7(projectRoot, ".agents"));
180157
- const hasClaudeDir = existsSync3(join7(projectRoot, ".claude"));
180873
+ const hasAgentsDir = existsSync3(join9(projectRoot, ".agents"));
180874
+ const hasClaudeDir = existsSync3(join9(projectRoot, ".claude"));
180158
180875
  let defaultTarget = "none";
180159
180876
  let recommendedTarget = "none";
180160
180877
  let message;
@@ -180181,10 +180898,10 @@ function discoverAgentTarget(projectRoot) {
180181
180898
  };
180182
180899
  }
180183
180900
  function collectMarkdownFiles(projectRoot, currentDir = ".") {
180184
- const absoluteDir = join7(projectRoot, currentDir);
180901
+ const absoluteDir = join9(projectRoot, currentDir);
180185
180902
  let entries;
180186
180903
  try {
180187
- entries = readdirSync4(absoluteDir);
180904
+ entries = readdirSync3(absoluteDir);
180188
180905
  } catch {
180189
180906
  return [];
180190
180907
  }
@@ -180195,7 +180912,7 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
180195
180912
  if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
180196
180913
  continue;
180197
180914
  }
180198
- const absolutePath = join7(projectRoot, relPath);
180915
+ const absolutePath = join9(projectRoot, relPath);
180199
180916
  let stats;
180200
180917
  try {
180201
180918
  stats = statSync2(absolutePath);
@@ -180282,6 +180999,491 @@ function isExcludedCodeFile(filePath, language) {
180282
180999
  return lower.includes(".generated.");
180283
181000
  }
180284
181001
 
181002
+ // src/setup/init-plan.ts
181003
+ import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
181004
+ import { join as join11, relative as relative3 } from "node:path";
181005
+
181006
+ // src/setup/skill-assets.ts
181007
+ import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
181008
+ import { join as join10 } from "node:path";
181009
+ function classifyManagedPath(projectRoot, relativePath) {
181010
+ const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
181011
+ let current = projectRoot;
181012
+ for (const [index, segment] of segments.entries()) {
181013
+ current = join10(current, segment);
181014
+ const isLast = index === segments.length - 1;
181015
+ let stats;
181016
+ try {
181017
+ stats = lstatSync2(current);
181018
+ } catch {
181019
+ return "absent";
181020
+ }
181021
+ if (stats.isSymbolicLink()) {
181022
+ return isLast ? "symlink" : "symlinked-parent";
181023
+ }
181024
+ if (!stats.isDirectory()) {
181025
+ return isLast ? "non-directory" : "blocked-parent";
181026
+ }
181027
+ }
181028
+ return segments.length === 0 ? "blocked-parent" : "directory";
181029
+ }
181030
+ function unmanageablePathMessage(path, kind) {
181031
+ switch (kind) {
181032
+ case "symlink":
181033
+ return `Skill directory ${path} is a symlink and was left in place.`;
181034
+ case "symlinked-parent":
181035
+ return `Skill directory ${path} sits under a symlinked directory and was left in place, because writing through it would leave the project root.`;
181036
+ case "non-directory":
181037
+ return `${path} exists but is not a directory, so it was left in place.`;
181038
+ case "blocked-parent":
181039
+ return `Skill directory ${path} cannot be reached because a parent path component is not a directory, so it was left in place.`;
181040
+ default:
181041
+ return `Skill directory ${path} was left in place.`;
181042
+ }
181043
+ }
181044
+ function listSkillFiles(directory) {
181045
+ const files = [];
181046
+ collectSkillFiles(directory, "", files);
181047
+ return files.toSorted();
181048
+ }
181049
+ function compareSkillTree(installedDir, templateDir) {
181050
+ const installedFiles = new Set(listSkillFiles(installedDir));
181051
+ const templateFiles = listSkillFiles(templateDir);
181052
+ const modified = [];
181053
+ const missing = [];
181054
+ for (const file of templateFiles) {
181055
+ if (!installedFiles.has(file)) {
181056
+ missing.push(file);
181057
+ continue;
181058
+ }
181059
+ installedFiles.delete(file);
181060
+ if (readFileOrUndefined(join10(installedDir, file)) !== readFileOrUndefined(join10(templateDir, file))) {
181061
+ modified.push(file);
181062
+ }
181063
+ }
181064
+ return { modified, missing, extra: [...installedFiles].toSorted() };
181065
+ }
181066
+ function applySkillOperation(projectRoot, packageRoot, operation) {
181067
+ if (operation.action !== "create" && operation.action !== "overwrite" && operation.action !== "remove") {
181068
+ return;
181069
+ }
181070
+ const kind = classifyManagedPath(projectRoot, operation.path);
181071
+ const destinationDir = join10(projectRoot, operation.path);
181072
+ if (operation.action === "remove") {
181073
+ if (kind === "directory") {
181074
+ rmSync(destinationDir, { recursive: true, force: true });
181075
+ }
181076
+ return;
181077
+ }
181078
+ if (kind !== "absent" && kind !== "directory") {
181079
+ return;
181080
+ }
181081
+ const skillName = operation.path.split(/[/\\]/).at(-1);
181082
+ if (skillName === undefined || skillName.length === 0) {
181083
+ return;
181084
+ }
181085
+ if (operation.action === "overwrite" && kind === "directory") {
181086
+ rmSync(destinationDir, { recursive: true, force: true });
181087
+ }
181088
+ mkdirSync3(destinationDir, { recursive: true });
181089
+ cpSync(join10(packageRoot, "templates", "skills", skillName), destinationDir, {
181090
+ recursive: true,
181091
+ force: true
181092
+ });
181093
+ }
181094
+ function collectSkillFiles(root, prefix, files) {
181095
+ let entries;
181096
+ try {
181097
+ entries = readdirSync4(join10(root, prefix), { withFileTypes: true });
181098
+ } catch {
181099
+ return;
181100
+ }
181101
+ for (const entry of entries.toSorted((left, right) => left.name < right.name ? -1 : 1)) {
181102
+ const relativePath = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
181103
+ if (entry.isDirectory()) {
181104
+ collectSkillFiles(root, relativePath, files);
181105
+ continue;
181106
+ }
181107
+ files.push(relativePath);
181108
+ }
181109
+ }
181110
+ function readFileOrUndefined(path) {
181111
+ try {
181112
+ return readFileSync6(path, "utf8");
181113
+ } catch {
181114
+ return;
181115
+ }
181116
+ }
181117
+
181118
+ // src/setup/init-plan.ts
181119
+ var CONFIG_FILE_NAME2 = "docbridge.config.json";
181120
+ var INIT_SKILL_NAMES = ["docbridge"];
181121
+ var LEGACY_SKILL_NAMES = [
181122
+ "docbridge-adopt",
181123
+ "docbridge-annotate",
181124
+ "docbridge-link",
181125
+ "docbridge-review",
181126
+ "docbridge-sync"
181127
+ ];
181128
+ function planInitCommand(input) {
181129
+ const packageRoot = input.packageRoot ?? resolvePackageRoot();
181130
+ const existingConfig = readExistingConfig(input.projectRoot);
181131
+ const messages = [];
181132
+ const nextSteps = [];
181133
+ let exitCode = 0;
181134
+ const agentResolution = input.command === "init" ? resolveInitAgentTarget(input.discovery, input.options) : resolveInitWithAgentTarget(input.discovery, input.options);
181135
+ if (agentResolution.error !== undefined) {
181136
+ return {
181137
+ command: input.command,
181138
+ configOps: [],
181139
+ skillOps: [],
181140
+ messages: [agentResolution.error],
181141
+ nextSteps: [],
181142
+ agentGuidance: [],
181143
+ exitCode: 1
181144
+ };
181145
+ }
181146
+ const agentTarget = agentResolution.target ?? "none";
181147
+ const configOps = input.command === "init" ? planConfigOperations({
181148
+ projectRoot: input.projectRoot,
181149
+ options: input.options,
181150
+ discovery: input.discovery,
181151
+ confirmedScope: input.confirmedScope,
181152
+ existingConfig,
181153
+ messages
181154
+ }) : [];
181155
+ if (configOps.some((operation) => operation.action === "create" || operation.action === "would-create")) {
181156
+ nextSteps.push("Review docbridge.config.json and adjust include.docs or include.code if needed.");
181157
+ nextSteps.push("Add @doc and @code annotations to link code and documentation.");
181158
+ nextSteps.push("Run docbridge check when links exist.");
181159
+ } else if (existingConfig.ok) {
181160
+ messages.push("Existing docbridge.config.json was left unchanged.");
181161
+ summarizeExistingConfig(existingConfig.config, messages);
181162
+ } else if (existingConfig.rawText !== undefined) {
181163
+ messages.push("Existing docbridge.config.json is invalid and was not modified.");
181164
+ for (const diagnostic of existingConfig.diagnostics) {
181165
+ messages.push(diagnostic.message);
181166
+ }
181167
+ messages.push("Repair the config manually or delete it before re-running init.");
181168
+ exitCode = 1;
181169
+ }
181170
+ const skillOps = planSkillOperations({
181171
+ projectRoot: input.projectRoot,
181172
+ packageRoot,
181173
+ agentTarget,
181174
+ options: input.options,
181175
+ messages
181176
+ });
181177
+ const agentGuidance = input.command === "init-with-agent" && agentTarget !== "none" ? buildAgentGuidance(input.projectRoot, agentTarget) : [];
181178
+ if (input.command === "init-with-agent") {
181179
+ nextSteps.push("Run the printed one-shot command or fallback prompt in your agent.");
181180
+ nextSteps.push("Let the docbridge skill confirm scope and create or improve docbridge.config.json.");
181181
+ }
181182
+ if (input.options.yes && input.command === "init" && configOps.length === 0 && existingConfig.rawText === undefined && (input.discovery.docs.ambiguous || input.discovery.code.ambiguous)) {
181183
+ exitCode = 1;
181184
+ }
181185
+ return {
181186
+ command: input.command,
181187
+ configOps,
181188
+ skillOps,
181189
+ messages,
181190
+ nextSteps,
181191
+ agentGuidance,
181192
+ exitCode
181193
+ };
181194
+ }
181195
+ function buildConfigFromScope(scope) {
181196
+ const code = {};
181197
+ for (const language of scope.languages) {
181198
+ code[language.language] = { patterns: [...language.patterns] };
181199
+ }
181200
+ return {
181201
+ include: {
181202
+ code,
181203
+ docs: [scope.docsPattern]
181204
+ }
181205
+ };
181206
+ }
181207
+ function listDistributableSkills(packageRoot) {
181208
+ const skillsRoot = join11(packageRoot, "templates", "skills");
181209
+ let entries;
181210
+ try {
181211
+ entries = readdirSync5(skillsRoot);
181212
+ } catch {
181213
+ return [];
181214
+ }
181215
+ return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync4(join11(skillsRoot, entry, "SKILL.md"))).toSorted();
181216
+ }
181217
+ function formatInitPlan(plan) {
181218
+ const lines = [];
181219
+ for (const message of plan.messages) {
181220
+ lines.push(message);
181221
+ }
181222
+ if (plan.configOps.length > 0) {
181223
+ lines.push("");
181224
+ lines.push("Config:");
181225
+ for (const operation of plan.configOps) {
181226
+ lines.push(formatFileOp(operation));
181227
+ if (operation.content !== undefined) {
181228
+ lines.push(operation.content);
181229
+ }
181230
+ }
181231
+ }
181232
+ if (plan.skillOps.length > 0) {
181233
+ lines.push("");
181234
+ lines.push("Skills:");
181235
+ for (const operation of plan.skillOps) {
181236
+ lines.push(formatFileOp(operation));
181237
+ }
181238
+ }
181239
+ if (plan.agentGuidance.length > 0) {
181240
+ lines.push("");
181241
+ lines.push("Agent setup:");
181242
+ for (const guidance of plan.agentGuidance) {
181243
+ lines.push("");
181244
+ lines.push(`${capitalize(guidance.agent)} destination: ${guidance.destination}`);
181245
+ lines.push(`One-shot command: ${guidance.oneShotCommand}`);
181246
+ lines.push("Fallback prompt:");
181247
+ lines.push(guidance.fallbackPrompt);
181248
+ }
181249
+ }
181250
+ if (plan.nextSteps.length > 0) {
181251
+ lines.push("");
181252
+ lines.push("Next steps:");
181253
+ for (const step of plan.nextSteps) {
181254
+ lines.push(`- ${step}`);
181255
+ }
181256
+ }
181257
+ return `${lines.join(`
181258
+ `)}
181259
+ `;
181260
+ }
181261
+ function planConfigOperations(input) {
181262
+ if (input.existingConfig.rawText !== undefined) {
181263
+ return [];
181264
+ }
181265
+ const scope = input.confirmedScope ?? inferConfirmedScope(input.discovery, input.options);
181266
+ if (scope === undefined) {
181267
+ if (input.discovery.docs.message !== undefined) {
181268
+ input.messages.push(input.discovery.docs.message);
181269
+ }
181270
+ if (input.discovery.code.message !== undefined) {
181271
+ input.messages.push(input.discovery.code.message);
181272
+ }
181273
+ if (input.options.yes) {
181274
+ input.messages.push("Automatic setup stopped because docs or code scope is ambiguous. Re-run interactively or confirm scope explicitly.");
181275
+ }
181276
+ return [];
181277
+ }
181278
+ const config = buildConfigFromScope(scope);
181279
+ const content = `${JSON.stringify({ $schema: "./schemas/docbridge.schema.json", ...config }, null, 2)}
181280
+ `;
181281
+ const action = input.options.dryRun ? "would-create" : "create";
181282
+ input.messages.push(`Docs scope: ${scope.docsPattern}`);
181283
+ input.messages.push(`Code scope: ${scope.languages.map((language) => `${language.language} (${language.patterns.join(", ")})`).join("; ")}`);
181284
+ return [
181285
+ {
181286
+ action,
181287
+ path: CONFIG_FILE_NAME2,
181288
+ content,
181289
+ reason: "Create docbridge.config.json from discovered scope."
181290
+ }
181291
+ ];
181292
+ }
181293
+ function inferConfirmedScope(discovery, options) {
181294
+ if (discovery.docs.ambiguous || discovery.code.ambiguous) {
181295
+ return;
181296
+ }
181297
+ if (discovery.docs.recommended === undefined || discovery.code.languages.length === 0) {
181298
+ return;
181299
+ }
181300
+ if (!options.yes) {
181301
+ return;
181302
+ }
181303
+ return {
181304
+ docsPattern: discovery.docs.recommended.pattern,
181305
+ languages: discovery.code.languages
181306
+ };
181307
+ }
181308
+ function planSkillOperations(input) {
181309
+ if (input.agentTarget === "none") {
181310
+ return [];
181311
+ }
181312
+ const destinations = agentDestinations(input.agentTarget);
181313
+ const availableSkills = new Set(listDistributableSkills(input.packageRoot));
181314
+ const operations = [];
181315
+ for (const destination of destinations) {
181316
+ for (const skillName of INIT_SKILL_NAMES) {
181317
+ if (!availableSkills.has(skillName)) {
181318
+ continue;
181319
+ }
181320
+ const destinationDir = join11(input.projectRoot, destination, skillName);
181321
+ const path = relative3(input.projectRoot, destinationDir);
181322
+ const kind = classifyManagedPath(input.projectRoot, path);
181323
+ if (kind !== "absent" && kind !== "directory") {
181324
+ input.messages.push(unmanageablePathMessage(path, kind));
181325
+ continue;
181326
+ }
181327
+ const exists = kind === "directory";
181328
+ let action;
181329
+ let reason;
181330
+ if (exists && input.options.force) {
181331
+ action = input.options.dryRun ? "would-overwrite" : "overwrite";
181332
+ reason = "Overwrite existing skill directory.";
181333
+ } else if (exists) {
181334
+ action = "skip";
181335
+ reason = "Skill directory already exists.";
181336
+ } else {
181337
+ action = input.options.dryRun ? "would-create" : "create";
181338
+ reason = "Install distributable skill template.";
181339
+ }
181340
+ operations.push({
181341
+ action,
181342
+ path,
181343
+ reason
181344
+ });
181345
+ }
181346
+ operations.push(...planLegacySkillOperations({
181347
+ projectRoot: input.projectRoot,
181348
+ destination,
181349
+ options: input.options,
181350
+ messages: input.messages
181351
+ }));
181352
+ }
181353
+ return operations;
181354
+ }
181355
+ function planLegacySkillOperations(input) {
181356
+ const operations = [];
181357
+ const leftover = [];
181358
+ for (const skillName of LEGACY_SKILL_NAMES) {
181359
+ const destinationDir = join11(input.projectRoot, input.destination, skillName);
181360
+ const path = relative3(input.projectRoot, destinationDir);
181361
+ const kind = classifyManagedPath(input.projectRoot, path);
181362
+ if (kind === "absent") {
181363
+ continue;
181364
+ }
181365
+ if (kind !== "directory") {
181366
+ input.messages.push(unmanageablePathMessage(path, kind));
181367
+ continue;
181368
+ }
181369
+ if (input.options.force) {
181370
+ operations.push({
181371
+ action: input.options.dryRun ? "would-remove" : "remove",
181372
+ path,
181373
+ reason: "Remove leftover skill from the previous five-skill layout."
181374
+ });
181375
+ } else {
181376
+ leftover.push(path);
181377
+ }
181378
+ }
181379
+ if (leftover.length > 0) {
181380
+ input.messages.push(`Legacy DocBridge skill directories were left in place (${leftover.join(", ")}). Re-run with --force to remove them after reviewing local edits.`);
181381
+ }
181382
+ return operations;
181383
+ }
181384
+ function buildAgentGuidance(projectRoot, agentTarget) {
181385
+ const guidance = [];
181386
+ if (agentTarget === "codex" || agentTarget === "both") {
181387
+ guidance.push({
181388
+ agent: "codex",
181389
+ destination: ".agents/skills/docbridge/",
181390
+ oneShotCommand: `cd ${projectRoot} && codex --prompt "Use the docbridge skill to adopt DocBridge in this repository."`,
181391
+ fallbackPrompt: buildFallbackPrompt(projectRoot, "codex")
181392
+ });
181393
+ }
181394
+ if (agentTarget === "claude" || agentTarget === "both") {
181395
+ guidance.push({
181396
+ agent: "claude",
181397
+ destination: ".claude/skills/docbridge/",
181398
+ oneShotCommand: `cd ${projectRoot} && /docbridge adopt DocBridge in this repository`,
181399
+ fallbackPrompt: buildFallbackPrompt(projectRoot, "claude")
181400
+ });
181401
+ }
181402
+ return guidance;
181403
+ }
181404
+ function buildFallbackPrompt(projectRoot, agent) {
181405
+ const invocation = agent === "claude" ? "Use the docbridge skill" : "Use the docbridge skill from .agents/skills/docbridge";
181406
+ return `${invocation} to adopt DocBridge in ${projectRoot}. Confirm docs and code scope, create or improve docbridge.config.json, and suggest the next linking steps.`;
181407
+ }
181408
+ function resolveInitAgentTarget(discovery, options) {
181409
+ if (options.agentTarget !== undefined) {
181410
+ return { target: options.agentTarget, error: undefined };
181411
+ }
181412
+ if (options.yes) {
181413
+ return { target: discovery.agent.defaultTarget, error: undefined };
181414
+ }
181415
+ return { target: discovery.agent.recommendedTarget, error: undefined };
181416
+ }
181417
+ function resolveInitWithAgentTarget(discovery, options) {
181418
+ if (options.agentTarget !== undefined) {
181419
+ if (options.agentTarget === "none") {
181420
+ return {
181421
+ target: undefined,
181422
+ error: "init-with-agent requires an agent target other than none."
181423
+ };
181424
+ }
181425
+ return { target: options.agentTarget, error: undefined };
181426
+ }
181427
+ if (discovery.agent.defaultTarget === "none") {
181428
+ if (options.yes) {
181429
+ return {
181430
+ target: undefined,
181431
+ error: "init-with-agent --yes requires an explicit agent target when no .agents/ or .claude/ directory exists. Pass --agent-target codex, claude, or both."
181432
+ };
181433
+ }
181434
+ return { target: discovery.agent.recommendedTarget, error: undefined };
181435
+ }
181436
+ return { target: discovery.agent.defaultTarget, error: undefined };
181437
+ }
181438
+ function readExistingConfig(projectRoot) {
181439
+ const configPath = join11(projectRoot, CONFIG_FILE_NAME2);
181440
+ let rawText;
181441
+ try {
181442
+ rawText = readFileSync7(configPath, "utf8");
181443
+ } catch {
181444
+ rawText = undefined;
181445
+ }
181446
+ const resolved = resolveConfig(rawText);
181447
+ return {
181448
+ rawText,
181449
+ ok: resolved.ok,
181450
+ config: resolved.config,
181451
+ diagnostics: resolved.diagnostics
181452
+ };
181453
+ }
181454
+ function summarizeExistingConfig(config, messages) {
181455
+ messages.push(`Docs scope: ${config.include.docs.join(", ")}`);
181456
+ const codeSummary = Object.entries(config.include.code).map(([language, entry]) => `${language}: ${entry?.patterns.join(", ") ?? ""}`).join("; ");
181457
+ messages.push(`Code scope: ${codeSummary}`);
181458
+ }
181459
+ function agentDestinations(agentTarget) {
181460
+ switch (agentTarget) {
181461
+ case "codex":
181462
+ return [".agents/skills"];
181463
+ case "claude":
181464
+ return [".claude/skills"];
181465
+ case "both":
181466
+ return [".agents/skills", ".claude/skills"];
181467
+ default:
181468
+ return [];
181469
+ }
181470
+ }
181471
+ function formatFileOp(operation) {
181472
+ let label = operation.action;
181473
+ if (operation.action === "would-create") {
181474
+ label = "would create";
181475
+ } else if (operation.action === "would-overwrite") {
181476
+ label = "would overwrite";
181477
+ } else if (operation.action === "would-remove") {
181478
+ label = "would remove";
181479
+ }
181480
+ const reason = operation.reason ? ` (${operation.reason})` : "";
181481
+ return `- ${label} ${operation.path}${reason}`;
181482
+ }
181483
+ function capitalize(value) {
181484
+ return value.charAt(0).toUpperCase() + value.slice(1);
181485
+ }
181486
+
180285
181487
  // src/cli/init.ts
180286
181488
  var AGENT_TARGETS = new Set(["codex", "claude", "both", "none"]);
180287
181489
  function parseInitOptions(args, command) {
@@ -180348,7 +181550,7 @@ function createDefaultPrompts(isInteractive = process.stdin.isTTY) {
180348
181550
  return defaultValue;
180349
181551
  }
180350
181552
  process.stdout.write(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"} `);
180351
- const answer = readFileSync5(0, "utf8").trim().toLowerCase();
181553
+ const answer = readFileSync8(0, "utf8").trim().toLowerCase();
180352
181554
  if (answer.length === 0) {
180353
181555
  return defaultValue;
180354
181556
  }
@@ -180365,7 +181567,7 @@ function createDefaultPrompts(isInteractive = process.stdin.isTTY) {
180365
181567
  `);
180366
181568
  }
180367
181569
  process.stdout.write(`Choice [${defaultChoice}]: `);
180368
- const answer = readFileSync5(0, "utf8").trim();
181570
+ const answer = readFileSync8(0, "utf8").trim();
180369
181571
  if (answer.length === 0) {
180370
181572
  return defaultChoice;
180371
181573
  }
@@ -180481,30 +181683,17 @@ function executePlan(projectRoot, plan, packageRoot) {
180481
181683
  if (operation.action === "skip") {
180482
181684
  continue;
180483
181685
  }
180484
- executeSkillOperation(projectRoot, packageRoot, operation);
181686
+ applySkillOperation(projectRoot, packageRoot, operation);
180485
181687
  }
180486
181688
  }
180487
181689
  function executeFileOperation(projectRoot, operation) {
180488
- const absolutePath = join8(projectRoot, operation.path);
181690
+ const absolutePath = join12(projectRoot, operation.path);
180489
181691
  if (operation.action === "create" && operation.content !== undefined) {
180490
- writeFileSync(absolutePath, operation.content, "utf8");
181692
+ writeFileSync2(absolutePath, operation.content, "utf8");
180491
181693
  }
180492
181694
  if (operation.action === "overwrite" && operation.content !== undefined) {
180493
- writeFileSync(absolutePath, operation.content, "utf8");
180494
- }
180495
- }
180496
- function executeSkillOperation(projectRoot, packageRoot, operation) {
180497
- if (operation.action !== "create" && operation.action !== "overwrite") {
180498
- return;
180499
- }
180500
- const skillName = operation.path.split("/").at(-1);
180501
- if (skillName === undefined) {
180502
- return;
181695
+ writeFileSync2(absolutePath, operation.content, "utf8");
180503
181696
  }
180504
- const sourceDir = join8(packageRoot, "templates", "skills", skillName);
180505
- const destinationDir = join8(projectRoot, operation.path);
180506
- mkdirSync2(destinationDir, { recursive: true });
180507
- cpSync(sourceDir, destinationDir, { recursive: true, force: true });
180508
181697
  }
180509
181698
  function resolveProjectRoot(root, command) {
180510
181699
  const projectRoot = resolve3(root);
@@ -180520,6 +181709,343 @@ function resolveProjectRoot(root, command) {
180520
181709
  return projectRoot;
180521
181710
  }
180522
181711
 
181712
+ // src/cli/upgrade.ts
181713
+ import { statSync as statSync4 } from "node:fs";
181714
+ import { resolve as resolve4 } from "node:path";
181715
+
181716
+ // src/setup/upgrade-plan.ts
181717
+ import { existsSync as existsSync5 } from "node:fs";
181718
+ import { join as join13, relative as relative4 } from "node:path";
181719
+ var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
181720
+ function reportCliVersion(currentVersion, latest) {
181721
+ if (latest.status !== "ok") {
181722
+ return { current: currentVersion, latest: undefined, status: "unknown" };
181723
+ }
181724
+ if (isNewerStableVersion(latest.latest, currentVersion)) {
181725
+ return { current: currentVersion, latest: latest.latest, status: "outdated" };
181726
+ }
181727
+ if (isNewerStableVersion(currentVersion, latest.latest)) {
181728
+ return { current: currentVersion, latest: latest.latest, status: "ahead" };
181729
+ }
181730
+ return { current: currentVersion, latest: latest.latest, status: "up-to-date" };
181731
+ }
181732
+ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
181733
+ return explicitTarget ?? discovery.agent.defaultTarget;
181734
+ }
181735
+ function inspectManagedSkill(input) {
181736
+ const destinationDir = join13(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
181737
+ const path = toRootRelative(input.projectRoot, destinationDir);
181738
+ const empty = {
181739
+ destination: input.destination,
181740
+ path,
181741
+ modifiedFiles: [],
181742
+ missingFiles: [],
181743
+ extraFiles: []
181744
+ };
181745
+ const kind = classifyManagedPath(input.projectRoot, path);
181746
+ if (kind !== "directory") {
181747
+ return { ...empty, state: kind };
181748
+ }
181749
+ if (!input.templateAvailable) {
181750
+ return { ...empty, state: "template-missing" };
181751
+ }
181752
+ const comparison = compareSkillTree(destinationDir, join13(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
181753
+ const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
181754
+ return {
181755
+ destination: input.destination,
181756
+ path,
181757
+ state: drifted ? "modified" : "up-to-date",
181758
+ modifiedFiles: comparison.modified,
181759
+ missingFiles: comparison.missing,
181760
+ extraFiles: comparison.extra
181761
+ };
181762
+ }
181763
+ function inspectLegacySkills(projectRoot, destination) {
181764
+ const reports = [];
181765
+ for (const skillName of LEGACY_SKILL_NAMES) {
181766
+ const destinationDir = join13(projectRoot, destination, skillName);
181767
+ const path = toRootRelative(projectRoot, destinationDir);
181768
+ const kind = classifyManagedPath(projectRoot, path);
181769
+ if (kind === "absent") {
181770
+ continue;
181771
+ }
181772
+ if (kind === "symlinked-parent" && !existsSync5(destinationDir)) {
181773
+ continue;
181774
+ }
181775
+ reports.push({ path, kind });
181776
+ }
181777
+ return reports;
181778
+ }
181779
+ function planUpgrade(input) {
181780
+ const mode = input.options.check ? "check" : "apply";
181781
+ const cli = reportCliVersion(input.currentVersion, input.latest);
181782
+ const agentTarget = resolveUpgradeAgentTarget(input.discovery, input.options.agentTarget);
181783
+ const templateAvailable = listDistributableSkills(input.packageRoot).includes(MANAGED_SKILL_NAME);
181784
+ const messages = [];
181785
+ const destinations = agentDestinations(agentTarget);
181786
+ const managedSkills = destinations.map((destination) => inspectManagedSkill({
181787
+ projectRoot: input.projectRoot,
181788
+ packageRoot: input.packageRoot,
181789
+ destination,
181790
+ templateAvailable
181791
+ }));
181792
+ const legacySkills = destinations.flatMap((destination) => inspectLegacySkills(input.projectRoot, destination));
181793
+ if (destinations.length === 0) {
181794
+ messages.push("No agent skill directory was selected, so no managed assets were inspected. Pass --agent-target codex, claude, or both to reconcile them.");
181795
+ }
181796
+ if (!templateAvailable && destinations.length > 0) {
181797
+ messages.push("The installed DocBridge package ships no docbridge skill template, so managed skills cannot be reconciled. Reinstall DocBridge.");
181798
+ }
181799
+ addVersionMessages(cli, input.guidance, messages);
181800
+ const pending = [];
181801
+ const operations = mode === "check" ? [] : planOperations({ managedSkills, legacySkills, options: input.options, pending });
181802
+ for (const report of managedSkills) {
181803
+ if (isUnmanageableState(report.state)) {
181804
+ messages.push(unmanageablePathMessage(report.path, report.state));
181805
+ }
181806
+ }
181807
+ for (const legacy of legacySkills) {
181808
+ if (legacy.kind !== "directory") {
181809
+ messages.push(unmanageablePathMessage(legacy.path, legacy.kind));
181810
+ }
181811
+ }
181812
+ return {
181813
+ mode,
181814
+ cli,
181815
+ guidance: input.guidance,
181816
+ managedSkills,
181817
+ legacySkills,
181818
+ operations,
181819
+ pending,
181820
+ messages,
181821
+ nextSteps: buildNextSteps({ mode, cli, options: input.options, pending }),
181822
+ requiresConfirmation: mode === "apply" && !input.options.dryRun && !input.options.yes && operations.some((operation) => operation.action === "overwrite" || operation.action === "remove"),
181823
+ exitCode: 0
181824
+ };
181825
+ }
181826
+ function planOperations(input) {
181827
+ const operations = [];
181828
+ for (const report of input.managedSkills) {
181829
+ if (report.state === "template-missing" || isUnmanageableState(report.state)) {
181830
+ continue;
181831
+ }
181832
+ if (report.state === "absent") {
181833
+ operations.push({
181834
+ action: input.options.dryRun ? "would-create" : "create",
181835
+ path: report.path,
181836
+ reason: "Install the managed docbridge skill."
181837
+ });
181838
+ continue;
181839
+ }
181840
+ if (!input.options.force) {
181841
+ input.pending.push(`${report.path} is ${report.state === "modified" ? "locally modified" : "already installed"} and was preserved. Re-run with --force to replace it with the packaged template.`);
181842
+ continue;
181843
+ }
181844
+ operations.push({
181845
+ action: input.options.dryRun ? "would-overwrite" : "overwrite",
181846
+ path: report.path,
181847
+ reason: "Replace the managed docbridge skill with the packaged template."
181848
+ });
181849
+ }
181850
+ for (const legacy of input.legacySkills) {
181851
+ if (legacy.kind !== "directory") {
181852
+ continue;
181853
+ }
181854
+ if (!input.options.force) {
181855
+ input.pending.push(`${legacy.path} is a leftover directory from the previous five-skill layout. Re-run with --force to remove it after reviewing local edits.`);
181856
+ continue;
181857
+ }
181858
+ operations.push({
181859
+ action: input.options.dryRun ? "would-remove" : "remove",
181860
+ path: legacy.path,
181861
+ reason: "Remove leftover skill from the previous five-skill layout."
181862
+ });
181863
+ }
181864
+ return operations;
181865
+ }
181866
+ function isUnmanageableState(state) {
181867
+ return state === "symlink" || state === "symlinked-parent" || state === "non-directory" || state === "blocked-parent";
181868
+ }
181869
+ function addVersionMessages(cli, guidance, messages) {
181870
+ if (cli.status === "outdated") {
181871
+ messages.push(`The running binary is older than the latest stable release. DocBridge does not upgrade itself: run \`${guidance.primaryCommand}\`, then re-run \`docbridge upgrade\` so managed assets are reconciled from the new binary.`);
181872
+ return;
181873
+ }
181874
+ if (cli.status === "unknown") {
181875
+ messages.push("The latest stable version could not be determined, so only local asset state is reported.");
181876
+ }
181877
+ }
181878
+ function buildNextSteps(input) {
181879
+ const nextSteps = [];
181880
+ if (input.cli.status === "outdated") {
181881
+ nextSteps.push("Upgrade the CLI with the command above, then re-run `docbridge upgrade`.");
181882
+ }
181883
+ if (input.mode === "check") {
181884
+ nextSteps.push("Run `docbridge upgrade --dry-run` to see the operations that would be applied.");
181885
+ }
181886
+ if (input.pending.length > 0) {
181887
+ nextSteps.push("Re-run with --force to apply the pending migrations listed above.");
181888
+ }
181889
+ if (input.mode === "apply" && input.options.dryRun) {
181890
+ nextSteps.push("Re-run without --dry-run to apply the plan.");
181891
+ }
181892
+ return nextSteps;
181893
+ }
181894
+ function formatUpgradePlan(plan) {
181895
+ const lines = [
181896
+ `DocBridge ${plan.cli.current} (latest stable: ${plan.cli.latest ?? "unknown"})`,
181897
+ `Status: ${plan.cli.status}`,
181898
+ ...formatUpgradeGuidance(plan.guidance)
181899
+ ];
181900
+ if (plan.messages.length > 0) {
181901
+ lines.push("", ...plan.messages);
181902
+ }
181903
+ appendSection(lines, "Managed skills:", plan.managedSkills.flatMap(formatManagedSkill));
181904
+ appendSection(lines, "Legacy skills:", plan.legacySkills.map((legacy) => `- ${legacy.path} (${legacy.kind})`));
181905
+ appendSection(lines, "Operations:", plan.operations.map((operation) => formatFileOp(operation)));
181906
+ appendSection(lines, "Pending migration:", plan.pending.map((entry) => `- ${entry}`));
181907
+ appendSection(lines, "Next steps:", plan.nextSteps.map((step) => `- ${step}`));
181908
+ return `${lines.join(`
181909
+ `)}
181910
+ `;
181911
+ }
181912
+ function formatManagedSkill(report) {
181913
+ const lines = [`- ${report.path}: ${report.state}`];
181914
+ appendFileList(lines, "changed", report.modifiedFiles);
181915
+ appendFileList(lines, "missing", report.missingFiles);
181916
+ appendFileList(lines, "extra", report.extraFiles);
181917
+ return lines;
181918
+ }
181919
+ function appendFileList(lines, label, files) {
181920
+ if (files.length > 0) {
181921
+ lines.push(` ${label}: ${files.join(", ")}`);
181922
+ }
181923
+ }
181924
+ function appendSection(lines, heading, entries) {
181925
+ if (entries.length === 0) {
181926
+ return;
181927
+ }
181928
+ lines.push("", heading, ...entries);
181929
+ }
181930
+ function toRootRelative(projectRoot, absolutePath) {
181931
+ return relative4(projectRoot, absolutePath).split("\\").join("/");
181932
+ }
181933
+
181934
+ // src/cli/upgrade.ts
181935
+ var AGENT_TARGETS2 = new Set(["codex", "claude", "both", "none"]);
181936
+ function parseUpgradeOptions(args) {
181937
+ const options = {
181938
+ root: ".",
181939
+ agentTarget: undefined,
181940
+ check: false,
181941
+ dryRun: false,
181942
+ yes: false,
181943
+ force: false
181944
+ };
181945
+ for (let index = 0;index < args.length; index += 1) {
181946
+ const arg = args[index];
181947
+ if (arg === undefined) {
181948
+ continue;
181949
+ }
181950
+ if (arg === "--check") {
181951
+ options.check = true;
181952
+ continue;
181953
+ }
181954
+ if (arg === "--dry-run") {
181955
+ options.dryRun = true;
181956
+ continue;
181957
+ }
181958
+ if (arg === "--yes") {
181959
+ options.yes = true;
181960
+ continue;
181961
+ }
181962
+ if (arg === "--force") {
181963
+ options.force = true;
181964
+ continue;
181965
+ }
181966
+ if (arg === "--root") {
181967
+ const root = args[index + 1];
181968
+ if (root === undefined) {
181969
+ throw new CliError("--root requires a path.", rootPathGuidance("upgrade"));
181970
+ }
181971
+ options.root = root;
181972
+ index += 1;
181973
+ continue;
181974
+ }
181975
+ if (arg === "--agent-target") {
181976
+ options.agentTarget = parseAgentTarget(args[index + 1]);
181977
+ index += 1;
181978
+ continue;
181979
+ }
181980
+ if (arg.startsWith("--")) {
181981
+ throw new CliError(`Unknown option: ${arg}`, commandHelpGuidance("upgrade"));
181982
+ }
181983
+ throw new CliError(`Unexpected argument: ${arg}`, commandHelpGuidance("upgrade"));
181984
+ }
181985
+ return options;
181986
+ }
181987
+ function runUpgrade(options, io, initRuntime, runtime) {
181988
+ const projectRoot = resolveProjectRoot2(options.root);
181989
+ const packageRoot = initRuntime.packageRoot ?? resolvePackageRoot();
181990
+ const plan = planUpgrade({
181991
+ projectRoot,
181992
+ packageRoot,
181993
+ currentVersion: runtime.currentVersion,
181994
+ latest: runtime.latest,
181995
+ guidance: detectUpgradeGuidance({
181996
+ packageRoot,
181997
+ projectRoot,
181998
+ ...runtime.currentDirectory !== undefined ? { currentDirectory: runtime.currentDirectory } : {},
181999
+ ...runtime.env !== undefined ? { env: runtime.env } : {}
182000
+ }),
182001
+ discovery: discoverRepository(projectRoot),
182002
+ options
182003
+ });
182004
+ if (plan.mode === "check" || options.dryRun) {
182005
+ io.stdout(formatUpgradePlan(plan));
182006
+ return plan.exitCode;
182007
+ }
182008
+ return applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime);
182009
+ }
182010
+ function applyUpgrade(projectRoot, packageRoot, plan, io, initRuntime) {
182011
+ if (plan.requiresConfirmation && !initRuntime.prompts.isInteractive) {
182012
+ throw new CliError("Replacing or removing managed skill directories requires confirmation.", upgradeConfirmationGuidance());
182013
+ }
182014
+ if (plan.requiresConfirmation && !initRuntime.prompts.confirm(`Apply ${plan.operations.length} operation(s) to managed skill directories?`, false)) {
182015
+ io.stdout(formatUpgradePlan(plan));
182016
+ io.stdout(`No changes were applied.
182017
+ `);
182018
+ return 0;
182019
+ }
182020
+ for (const operation of plan.operations) {
182021
+ applySkillOperation(projectRoot, packageRoot, operation);
182022
+ }
182023
+ io.stdout(formatUpgradePlan(plan));
182024
+ return plan.exitCode;
182025
+ }
182026
+ function parseAgentTarget(value) {
182027
+ if (value === undefined) {
182028
+ throw new CliError("--agent-target requires a value.", agentTargetGuidance("upgrade"));
182029
+ }
182030
+ if (!AGENT_TARGETS2.has(value)) {
182031
+ throw new CliError(`Unknown agent target: ${value}. Supported values: codex, claude, both, none.`, agentTargetGuidance("upgrade"));
182032
+ }
182033
+ return value;
182034
+ }
182035
+ function resolveProjectRoot2(root) {
182036
+ const projectRoot = resolve4(root);
182037
+ let stats;
182038
+ try {
182039
+ stats = statSync4(projectRoot);
182040
+ } catch {
182041
+ throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
182042
+ }
182043
+ if (!stats.isDirectory()) {
182044
+ throw new CliError(`Root path is not a directory: ${root}`, rootPathGuidance("upgrade"));
182045
+ }
182046
+ return projectRoot;
182047
+ }
182048
+
180523
182049
  // src/cli/index.ts
180524
182050
  var VERSION = package_default.version;
180525
182051
  function parseCheckOptions(args) {
@@ -180534,11 +182060,11 @@ function parseContextOptions(args) {
180534
182060
  function parseGraphOptions(args) {
180535
182061
  return parseCommandOptions("graph", args);
180536
182062
  }
180537
- function resolveProjectRoot2(root, command) {
180538
- const projectRoot = resolve4(root);
182063
+ function resolveProjectRoot3(root, command) {
182064
+ const projectRoot = resolve5(root);
180539
182065
  let stats;
180540
182066
  try {
180541
- stats = statSync4(projectRoot);
182067
+ stats = statSync5(projectRoot);
180542
182068
  } catch {
180543
182069
  throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
180544
182070
  }
@@ -180548,7 +182074,7 @@ function resolveProjectRoot2(root, command) {
180548
182074
  return projectRoot;
180549
182075
  }
180550
182076
  function runCheck(options, io) {
180551
- const projectRoot = resolveProjectRoot2(options.root, "check");
182077
+ const projectRoot = resolveProjectRoot3(options.root, "check");
180552
182078
  const result = check({ projectRoot, audit: options.audit });
180553
182079
  if (options.json) {
180554
182080
  io.stdout(`${JSON.stringify(result, null, 2)}
@@ -180560,9 +182086,10 @@ function runCheck(options, io) {
180560
182086
 
180561
182087
  ` : "";
180562
182088
  io.stdout(`${body}${formatSummary(result.summary)}
182089
+ See \`docbridge docs show troubleshooting\` for diagnostic codes and fixes.
180563
182090
  `);
180564
- if (result.diagnostics.some((diagnostic) => diagnostic.code === "config_file_invalid")) {
180565
- const guidance = existsSync4(join9(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
182091
+ const guidance = repairGuidanceFor(result.diagnostics, projectRoot);
182092
+ if (guidance !== undefined) {
180566
182093
  io.stderr(`${guidance}
180567
182094
  `);
180568
182095
  }
@@ -180570,10 +182097,10 @@ function runCheck(options, io) {
180570
182097
  return result.summary.errors > 0 ? 1 : 0;
180571
182098
  }
180572
182099
  function runFileCommand(command, options, io, execute, render) {
180573
- const projectRoot = resolveProjectRoot2(options.root, command);
182100
+ const projectRoot = resolveProjectRoot3(options.root, command);
180574
182101
  const inputFiles = [...options.files];
180575
182102
  if (options.stdin) {
180576
- const readStdin = io.stdin ?? (() => readFileSync6(0, "utf8"));
182103
+ const readStdin = io.stdin ?? (() => readFileSync9(0, "utf8"));
180577
182104
  inputFiles.push(...readStdin().split(`
180578
182105
  `));
180579
182106
  }
@@ -180653,8 +182180,18 @@ function runGraph(options, io) {
180653
182180
  return 0;
180654
182181
  });
180655
182182
  }
182183
+ function repairGuidanceFor(diagnostics, projectRoot) {
182184
+ const invalid = diagnostics.filter((diagnostic) => diagnostic.code === "config_file_invalid");
182185
+ if (invalid.some((diagnostic) => diagnostic.target === LINK_MANIFEST_FILE_NAME)) {
182186
+ return manifestRepairGuidance();
182187
+ }
182188
+ if (invalid.length === 0) {
182189
+ return;
182190
+ }
182191
+ return existsSync6(join14(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
182192
+ }
180656
182193
  function unknownCommandGuidance(command) {
180657
- const suggestion = nearestSubcommand(command);
182194
+ const suggestion = nearestMatch(command, SUBCOMMANDS);
180658
182195
  const lines = ["Available commands:", ` ${SUBCOMMANDS.join(", ")}`];
180659
182196
  if (suggestion !== undefined) {
180660
182197
  lines.push("", `Did you mean \`${suggestion}\`?`);
@@ -180663,49 +182200,6 @@ function unknownCommandGuidance(command) {
180663
182200
  return lines.join(`
180664
182201
  `);
180665
182202
  }
180666
- function nearestSubcommand(input) {
180667
- const ranked = SUBCOMMANDS.map((command, index) => ({
180668
- command,
180669
- distance: editDistance(input, command),
180670
- index
180671
- })).toSorted((left, right) => left.distance - right.distance || left.index - right.index);
180672
- const abbreviation = ranked.find(({ command }) => isOrderedAbbreviation(input, command));
180673
- if (abbreviation !== undefined) {
180674
- return abbreviation.command;
180675
- }
180676
- const best = ranked[0];
180677
- if (best === undefined) {
180678
- return;
180679
- }
180680
- const closeEnough = best.distance <= Math.max(1, Math.floor(best.command.length / 2));
180681
- return closeEnough ? best.command : undefined;
180682
- }
180683
- function isOrderedAbbreviation(input, command) {
180684
- if (input.length < 3 || input.length >= command.length) {
180685
- return false;
180686
- }
180687
- let commandIndex = 0;
180688
- for (const character of input) {
180689
- const matchIndex = command.indexOf(character, commandIndex);
180690
- if (matchIndex === -1) {
180691
- return false;
180692
- }
180693
- commandIndex = matchIndex + 1;
180694
- }
180695
- return true;
180696
- }
180697
- function editDistance(left, right) {
180698
- let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
180699
- for (let leftIndex = 1;leftIndex <= left.length; leftIndex += 1) {
180700
- const current = [leftIndex];
180701
- for (let rightIndex = 1;rightIndex <= right.length; rightIndex += 1) {
180702
- const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
180703
- current[rightIndex] = Math.min((current[rightIndex - 1] ?? Number.POSITIVE_INFINITY) + 1, (previous[rightIndex] ?? Number.POSITIVE_INFINITY) + 1, (previous[rightIndex - 1] ?? Number.POSITIVE_INFINITY) + substitutionCost);
180704
- }
180705
- previous = current;
180706
- }
180707
- return previous[right.length] ?? 0;
180708
- }
180709
182203
  var COMMAND_HANDLERS = {
180710
182204
  check: (args, io) => runCheck(parseCheckOptions(args), io),
180711
182205
  related: (args, io) => runRelated(parseRelatedOptions(args), io),
@@ -180714,6 +182208,12 @@ var COMMAND_HANDLERS = {
180714
182208
  docs: (args, io) => runDocs(parseDocsCommand(args), io),
180715
182209
  init: (args, io, initRuntime) => runInit(parseInitOptions(args, "init"), io, initRuntime),
180716
182210
  "init-with-agent": (args, io, initRuntime) => runInitWithAgent(parseInitOptions(args, "init-with-agent"), io, initRuntime),
182211
+ upgrade: (args, io, initRuntime, cliRuntime) => runUpgrade(parseUpgradeOptions(args), io, initRuntime, {
182212
+ latest: cliRuntime.latest ?? { status: "unavailable", source: "network" },
182213
+ currentVersion: VERSION,
182214
+ ...cliRuntime.env !== undefined ? { env: cliRuntime.env } : {},
182215
+ ...cliRuntime.currentDirectory !== undefined ? { currentDirectory: cliRuntime.currentDirectory } : {}
182216
+ }),
180717
182217
  lsp: (args) => {
180718
182218
  if (args.length > 0) {
180719
182219
  throw new CliError("lsp takes no options.", commandHelpGuidance("lsp"));
@@ -180725,11 +182225,12 @@ var COMMAND_HANDLERS = {
180725
182225
  function run(argv, io = {
180726
182226
  stdout: (text) => process.stdout.write(text),
180727
182227
  stderr: (text) => process.stderr.write(text)
180728
- }, initRuntime = { prompts: createDefaultPrompts() }) {
182228
+ }, initRuntime = { prompts: createDefaultPrompts() }, cliRuntime = {}) {
180729
182229
  const [command, ...rest] = argv;
180730
182230
  try {
180731
182231
  if (command === undefined || command === "--help" || command === "-h") {
180732
182232
  io.stdout(GLOBAL_HELP);
182233
+ writeUpdateNotice(argv, io, initRuntime, cliRuntime);
180733
182234
  return 0;
180734
182235
  }
180735
182236
  if (isSubcommand(command) && hasHelpFlag(rest)) {
@@ -180739,10 +182240,13 @@ function run(argv, io = {
180739
182240
  if (command === "--version" || command === "-v") {
180740
182241
  io.stdout(`${VERSION}
180741
182242
  `);
182243
+ writeUpdateNotice(argv, io, initRuntime, cliRuntime);
180742
182244
  return 0;
180743
182245
  }
180744
182246
  if (isSubcommand(command)) {
180745
- return COMMAND_HANDLERS[command](rest, io, initRuntime);
182247
+ const exitCode = COMMAND_HANDLERS[command](rest, io, initRuntime, cliRuntime);
182248
+ writeUpdateNotice(argv, io, initRuntime, cliRuntime);
182249
+ return exitCode;
180746
182250
  }
180747
182251
  throw new CliError(`Unknown command: ${command}`, unknownCommandGuidance(command));
180748
182252
  } catch (error) {
@@ -180756,8 +182260,59 @@ function run(argv, io = {
180756
182260
  return 1;
180757
182261
  }
180758
182262
  }
182263
+ function rootOptionOf(argv, currentDirectory) {
182264
+ const flagIndex = argv.indexOf("--root");
182265
+ const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
182266
+ return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
182267
+ }
182268
+ function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
182269
+ const latest = cliRuntime.latest;
182270
+ if (latest === undefined || latest.status !== "ok") {
182271
+ return;
182272
+ }
182273
+ if (!decideUpdateCheck({
182274
+ argv,
182275
+ ...cliRuntime.env !== undefined ? { env: cliRuntime.env } : {},
182276
+ isTty: cliRuntime.isTty ?? false
182277
+ }).enabled) {
182278
+ return;
182279
+ }
182280
+ const packageRoot = initRuntime.packageRoot ?? resolvePackageRoot();
182281
+ const currentDirectory = cliRuntime.currentDirectory ?? process.cwd();
182282
+ const notice = formatUpdateNotice({
182283
+ current: VERSION,
182284
+ latest: latest.latest,
182285
+ guidance: detectUpgradeGuidance({
182286
+ packageRoot,
182287
+ projectRoot: rootOptionOf(argv, currentDirectory),
182288
+ currentDirectory,
182289
+ ...cliRuntime.env !== undefined ? { env: cliRuntime.env } : {}
182290
+ })
182291
+ });
182292
+ if (notice !== undefined) {
182293
+ io.stderr(notice);
182294
+ }
182295
+ }
182296
+ async function resolveLatestForInvocation(argv, env, isTty) {
182297
+ if (isUpdateCheckOptedOut(env)) {
182298
+ return;
182299
+ }
182300
+ if (argv[0] === "upgrade" && !hasHelpFlag(argv.slice(1))) {
182301
+ return resolveLatestStableVersion({ forceRefresh: true });
182302
+ }
182303
+ if (!decideUpdateCheck({ argv, env, isTty }).enabled) {
182304
+ return;
182305
+ }
182306
+ return resolveLatestStableVersion();
182307
+ }
180759
182308
  if (__require.main == __require.module) {
180760
- process.exitCode = run(process.argv.slice(2));
182309
+ const argv = process.argv.slice(2);
182310
+ const isTty = process.stderr.isTTY === true;
182311
+ const latest = await resolveLatestForInvocation(argv, process.env, isTty);
182312
+ process.exitCode = run(argv, {
182313
+ stdout: (text) => process.stdout.write(text),
182314
+ stderr: (text) => process.stderr.write(text)
182315
+ }, { prompts: createDefaultPrompts() }, { ...latest !== undefined ? { latest } : {}, env: process.env, isTty });
180761
182316
  }
180762
182317
  export {
180763
182318
  run,