docbridge 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-08-18
11
+
12
+ ### Added
13
+
14
+ - The VS Code-compatible extension `salan70.docbridge` is published on Visual
15
+ Studio Marketplace.
16
+ - Packaged user docs now cover linking workflow and semantic link review
17
+ (`docbridge docs show linking-workflow`, `docbridge docs show link-review`),
18
+ plus supported and unsupported `@doc` declaration forms, the heading-anchor
19
+ algorithm, and named link-authoring diagnostic codes.
20
+ - Human-readable `docbridge check` output ends with a pointer to
21
+ `docbridge docs show troubleshooting`. `check --json` is unchanged.
22
+
23
+ ### Changed
24
+
25
+ - `docbridge init` and `docbridge init-with-agent` install one skill,
26
+ `docbridge`, instead of the five companion skills. The skill routes adopt,
27
+ discover-and-link, annotate, review, and sync work; facts about the binary
28
+ come from `docbridge docs show`.
29
+
30
+ ### Fixed
31
+
32
+ - `just package-vsix` bundles `vscode-languageclient` into the editor client.
33
+ Packaging with `--no-dependencies` previously omitted the module, so the
34
+ installed VSIX failed to activate.
35
+ - `docs/user/annotations.md` no longer claims GitHub-style numeric suffixes for
36
+ duplicate headings. Duplicate anchors in one file are `duplicate_doc_anchor`.
37
+
38
+ ### Upgrading
39
+
40
+ - Existing installs keep the five legacy skill directories
41
+ (`docbridge-adopt`, `docbridge-annotate`, `docbridge-link`,
42
+ `docbridge-review`, `docbridge-sync`) until `docbridge init --force` is run.
43
+ Review locally edited copies first. Symlinked skill directories are reported
44
+ and left untouched.
45
+
10
46
  ## [0.7.0] - 2026-08-16
11
47
 
12
48
  ### Added
@@ -315,7 +351,8 @@ Initial release of the SpecLink CLI.
315
351
  - `speclink check` command with `--root`, `--json`, and `--audit` options.
316
352
  - `speclink --version` (alias `-v`) and `speclink --help` (alias `-h`).
317
353
 
318
- [Unreleased]: https://github.com/salan70/docbridge/compare/v0.7.0...HEAD
354
+ [Unreleased]: https://github.com/salan70/docbridge/compare/v0.8.0...HEAD
355
+ [0.8.0]: https://github.com/salan70/docbridge/releases/tag/v0.8.0
319
356
  [0.7.0]: https://github.com/salan70/docbridge/releases/tag/v0.7.0
320
357
  [0.6.1]: https://github.com/salan70/docbridge/releases/tag/v0.6.1
321
358
  [0.6.0]: https://github.com/salan70/docbridge/releases/tag/v0.6.0
package/README.md CHANGED
@@ -40,9 +40,8 @@ Run first-time setup in the project root:
40
40
  bunx docbridge init
41
41
  ```
42
42
 
43
- For agent-guided adoption, install `docbridge-adopt` and print setup commands.
44
- The `docbridge-adopt` skill finishes adoption and installs the remaining
45
- DocBridge skills after scope is confirmed:
43
+ For agent-guided adoption, install the `docbridge` skill and print setup
44
+ commands. The skill routes adopt, link, review, and sync work:
46
45
 
47
46
  ```sh
48
47
  bunx docbridge init-with-agent
@@ -316,11 +315,10 @@ DocBridge's link graph is built to be consumed by AI coding agents:
316
315
  - [docs/integrations](docs/integrations) — recipes for Claude Code, Codex,
317
316
  and CI: gate triage with `docbridge related --gate`, counterpart content with
318
317
  `docbridge context`, and PR reporting.
319
- - [templates/skills](templates/skills) — distributable agent skills installed by
320
- `docbridge init`; `docbridge init-with-agent` installs `docbridge-adopt`
321
- alone, then `docbridge-adopt` installs the companion skills:
322
- `docbridge-annotate`, `docbridge-link`, `docbridge-review`, and
323
- `docbridge-sync`.
318
+ - [templates/skills](templates/skills) — the distributable `docbridge` skill
319
+ installed by `docbridge init` and `docbridge init-with-agent`. Facts about
320
+ the binary live in `docbridge docs show`. Existing five-skill installs remain
321
+ until `docbridge init --force`.
324
322
 
325
323
  This repository dogfoods the skills under `.claude/` and `.agents/`, and keeps
326
324
  its guardrail in the Git `pre-commit` hook under `.githooks/` rather than in
@@ -340,24 +338,24 @@ options; the project root comes from the editor's `initialize` request.
340
338
  `docbridge check` is unchanged.
341
339
 
342
340
  A VS Code-compatible extension lives in [editors/vscode](editors/vscode). It
343
- packages the language server into a VSIX for VS Code and Cursor. The extension
344
- has not yet been published to VS Code Marketplace. Until the first Marketplace
345
- publication, stage the supported `dist/bin/darwin-arm64` and
346
- `dist/bin/linux-x64` scanner artifacts, then build and install the VSIX
347
- locally:
341
+ packages the language server into a VSIX for VS Code and Cursor. Install
342
+ [`salan70.docbridge`](https://marketplace.visualstudio.com/items?itemName=salan70.docbridge)
343
+ from VS Code Marketplace. Bun must be available to the editor; set
344
+ `docbridge.bunPath` if the GUI cannot find `bun` on `PATH`.
345
+
346
+ Contributors can still stage the supported `dist/bin/darwin-arm64` and
347
+ `dist/bin/linux-x64` scanner artifacts and build a local VSIX:
348
348
 
349
349
  ```sh
350
350
  just package-vsix
351
351
  just verify-vsix
352
352
  ```
353
353
 
354
- Install the generated file from `editors/vscode/.tmp/out/` with **Extensions:
355
- Install from VSIX...** in VS Code or Cursor. The first Marketplace publication
356
- is intentionally manual; the repository provides a publish command for that
357
- release process. Open VSX delivery is out of scope. Zed integration is tracked
358
- separately and is not yet implemented. MCP delivery is out of scope until a
359
- concrete consumer requires a long-lived tool server. Full LSP behavior is specified in
360
- [docs/specs/lsp.md](docs/specs/lsp.md).
354
+ Registry publication remains manual (`VSCE_PAT=<token> just
355
+ publish-vscode-extension`). Open VSX delivery is out of scope. Zed integration
356
+ is tracked separately and is not yet implemented. MCP delivery is out of scope
357
+ until a concrete consumer requires a long-lived tool server. Full LSP behavior
358
+ is specified in [docs/specs/lsp.md](docs/specs/lsp.md).
361
359
 
362
360
  ## Diagnostics
363
361
 
@@ -417,7 +415,6 @@ Completed v0.1–v0.6 capabilities are documented above and in
417
415
 
418
416
  Remaining editor delivery work:
419
417
 
420
- - Publish the first verified VSIX to VS Code Marketplace
421
418
  - Follow-up automation for GitHub Release VSIX attachment and registry
422
419
  publishing after the manual flow is proven
423
420
  - Add a separate Zed integration path
@@ -1,3 +1,3 @@
1
- bc9e7548f993e48d280c3a487be4cc90a3e6549491c6f93f0e518ccc4239d991 docbridge-swift-scanner
2
- ceb237b7e5b2047d091f961c0a43ba436b8c624c741cc7f864e5783d9248a4eb docbridge_dart_scanner
1
+ 4b0ab3efbe865e9fb0cc48a6a0d37146c72a4255479e1c78c27b6c20e7e62604 docbridge-swift-scanner
2
+ c764c46477f522c8a82b62e629ff69907c0511fbc58b005ee6eba04d0e08dbef docbridge_dart_scanner
3
3
  a1b129d980c981a2d24f0c9682e614a9695d4e9f9b8174d61b0dacb3fc9e3c79 docbridge-rust-scanner
@@ -1,3 +1,3 @@
1
- 114f780cebfb8834f200ef60f3a828ca7dc4668b23f6e646971e3e56c377b37a docbridge-swift-scanner
1
+ 9b7f3eea8df6ec2a07bd243a46b149fe3468240c6e0293390dc58594140519de docbridge-swift-scanner
2
2
  095c4c172da615be155bee15dcce7b99faaf2a8bb26f3cad3e0b17881495ddf2 docbridge_dart_scanner
3
3
  d89a1146ed10dcb01151ef5351a746862c3955286431c47f3cf91cf6639866ba docbridge-rust-scanner
package/dist/index.js CHANGED
@@ -175581,7 +175581,7 @@ import { join as join9, resolve as resolve4 } from "node:path";
175581
175581
  // package.json
175582
175582
  var package_default = {
175583
175583
  name: "docbridge",
175584
- version: "0.7.0",
175584
+ version: "0.8.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",
@@ -179143,18 +179143,18 @@ import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node
179143
179143
  import { join as join6 } from "node:path";
179144
179144
 
179145
179145
  // src/core/init-plan.ts
179146
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
179146
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
179147
179147
  import { dirname as dirname3, join as join5, relative as relative3 } from "node:path";
179148
179148
  import { fileURLToPath as fileURLToPath4 } from "node:url";
179149
179149
  var CONFIG_FILE_NAME2 = "docbridge.config.json";
179150
- var INIT_SKILL_NAMES = [
179150
+ var INIT_SKILL_NAMES = ["docbridge"];
179151
+ var LEGACY_SKILL_NAMES = [
179151
179152
  "docbridge-adopt",
179152
179153
  "docbridge-annotate",
179153
179154
  "docbridge-link",
179154
179155
  "docbridge-review",
179155
179156
  "docbridge-sync"
179156
179157
  ];
179157
- var INIT_WITH_AGENT_SKILL_NAMES = ["docbridge-adopt"];
179158
179158
  function planInitCommand(input) {
179159
179159
  const packageRoot = input.packageRoot ?? resolvePackageRoot();
179160
179160
  const existingConfig = readExistingConfig(input.projectRoot);
@@ -179198,16 +179198,16 @@ function planInitCommand(input) {
179198
179198
  exitCode = 1;
179199
179199
  }
179200
179200
  const skillOps = planSkillOperations({
179201
- command: input.command,
179202
179201
  projectRoot: input.projectRoot,
179203
179202
  packageRoot,
179204
179203
  agentTarget,
179205
- options: input.options
179204
+ options: input.options,
179205
+ messages
179206
179206
  });
179207
179207
  const agentGuidance = input.command === "init-with-agent" && agentTarget !== "none" ? buildAgentGuidance(input.projectRoot, agentTarget) : [];
179208
179208
  if (input.command === "init-with-agent") {
179209
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.");
179210
+ nextSteps.push("Let the docbridge skill confirm scope and create or improve docbridge.config.json.");
179211
179211
  }
179212
179212
  if (input.options.yes && input.command === "init" && configOps.length === 0 && existingConfig.rawText === undefined && (input.discovery.docs.ambiguous || input.discovery.code.ambiguous)) {
179213
179213
  exitCode = 1;
@@ -179262,7 +179262,7 @@ function listDistributableSkills(packageRoot) {
179262
179262
  } catch {
179263
179263
  return [];
179264
179264
  }
179265
- return entries.filter((entry) => entry.startsWith("docbridge-")).filter((entry) => existsSync2(join5(skillsRoot, entry, "SKILL.md"))).toSorted();
179265
+ return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync2(join5(skillsRoot, entry, "SKILL.md"))).toSorted();
179266
179266
  }
179267
179267
  function formatInitPlan(plan) {
179268
179268
  const lines = [];
@@ -179359,16 +179359,20 @@ function planSkillOperations(input) {
179359
179359
  if (input.agentTarget === "none") {
179360
179360
  return [];
179361
179361
  }
179362
- const skillNames = input.command === "init" ? [...INIT_SKILL_NAMES] : [...INIT_WITH_AGENT_SKILL_NAMES];
179363
179362
  const destinations = agentDestinations(input.agentTarget);
179364
179363
  const availableSkills = new Set(listDistributableSkills(input.packageRoot));
179365
179364
  const operations = [];
179366
179365
  for (const destination of destinations) {
179367
- for (const skillName of skillNames) {
179366
+ for (const skillName of INIT_SKILL_NAMES) {
179368
179367
  if (!availableSkills.has(skillName)) {
179369
179368
  continue;
179370
179369
  }
179371
179370
  const destinationDir = join5(input.projectRoot, destination, skillName);
179371
+ const path = relative3(input.projectRoot, destinationDir);
179372
+ if (isSymlink(destinationDir)) {
179373
+ input.messages.push(`Skill directory ${path} is a symlink and was left in place.`);
179374
+ continue;
179375
+ }
179372
179376
  const exists = existsSync2(destinationDir);
179373
179377
  let action;
179374
179378
  let reason;
@@ -179384,10 +179388,44 @@ function planSkillOperations(input) {
179384
179388
  }
179385
179389
  operations.push({
179386
179390
  action,
179387
- path: relative3(input.projectRoot, destinationDir),
179391
+ path,
179388
179392
  reason
179389
179393
  });
179390
179394
  }
179395
+ operations.push(...planLegacySkillOperations({
179396
+ projectRoot: input.projectRoot,
179397
+ destination,
179398
+ options: input.options,
179399
+ messages: input.messages
179400
+ }));
179401
+ }
179402
+ return operations;
179403
+ }
179404
+ function planLegacySkillOperations(input) {
179405
+ const operations = [];
179406
+ const leftover = [];
179407
+ for (const skillName of LEGACY_SKILL_NAMES) {
179408
+ const destinationDir = join5(input.projectRoot, input.destination, skillName);
179409
+ const path = relative3(input.projectRoot, destinationDir);
179410
+ if (isSymlink(destinationDir)) {
179411
+ input.messages.push(`Skill directory ${path} is a symlink and was left in place.`);
179412
+ continue;
179413
+ }
179414
+ if (!existsSync2(destinationDir)) {
179415
+ continue;
179416
+ }
179417
+ if (input.options.force) {
179418
+ operations.push({
179419
+ action: input.options.dryRun ? "would-remove" : "remove",
179420
+ path,
179421
+ reason: "Remove leftover skill from the previous five-skill layout."
179422
+ });
179423
+ } else {
179424
+ leftover.push(path);
179425
+ }
179426
+ }
179427
+ if (leftover.length > 0) {
179428
+ input.messages.push(`Legacy DocBridge skill directories were left in place (${leftover.join(", ")}). Re-run with --force to remove them after reviewing local edits.`);
179391
179429
  }
179392
179430
  return operations;
179393
179431
  }
@@ -179396,27 +179434,24 @@ function buildAgentGuidance(projectRoot, agentTarget) {
179396
179434
  if (agentTarget === "codex" || agentTarget === "both") {
179397
179435
  guidance.push({
179398
179436
  agent: "codex",
179399
- destination: ".agents/skills/docbridge-adopt/",
179400
- oneShotCommand: `cd ${projectRoot} && $docbridge_adopt_prompt`,
179437
+ destination: ".agents/skills/docbridge/",
179438
+ oneShotCommand: `cd ${projectRoot} && codex --prompt "Use the docbridge skill to adopt DocBridge in this repository."`,
179401
179439
  fallbackPrompt: buildFallbackPrompt(projectRoot, "codex")
179402
179440
  });
179403
179441
  }
179404
179442
  if (agentTarget === "claude" || agentTarget === "both") {
179405
179443
  guidance.push({
179406
179444
  agent: "claude",
179407
- destination: ".claude/skills/docbridge-adopt/",
179408
- oneShotCommand: `cd ${projectRoot} && /docbridge-adopt`,
179445
+ destination: ".claude/skills/docbridge/",
179446
+ oneShotCommand: `cd ${projectRoot} && /docbridge adopt DocBridge in this repository`,
179409
179447
  fallbackPrompt: buildFallbackPrompt(projectRoot, "claude")
179410
179448
  });
179411
179449
  }
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
179450
  return guidance;
179416
179451
  }
179417
179452
  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.`;
179453
+ const invocation = agent === "claude" ? "Use the docbridge skill" : "Use the docbridge skill from .agents/skills/docbridge";
179454
+ return `${invocation} to adopt DocBridge in ${projectRoot}. Confirm docs and code scope, create or improve docbridge.config.json, and suggest the next linking steps.`;
179420
179455
  }
179421
179456
  function resolveInitAgentTarget(discovery, options) {
179422
179457
  if (options.agentTarget !== undefined) {
@@ -179481,12 +179516,21 @@ function agentDestinations(agentTarget) {
179481
179516
  return [];
179482
179517
  }
179483
179518
  }
179519
+ function isSymlink(path) {
179520
+ try {
179521
+ return lstatSync2(path).isSymbolicLink();
179522
+ } catch {
179523
+ return false;
179524
+ }
179525
+ }
179484
179526
  function formatFileOp(operation) {
179485
179527
  let label = operation.action;
179486
179528
  if (operation.action === "would-create") {
179487
179529
  label = "would create";
179488
179530
  } else if (operation.action === "would-overwrite") {
179489
179531
  label = "would overwrite";
179532
+ } else if (operation.action === "would-remove") {
179533
+ label = "would remove";
179490
179534
  }
179491
179535
  const reason = operation.reason ? ` (${operation.reason})` : "";
179492
179536
  return `- ${label} ${operation.path}${reason}`;
@@ -179859,9 +179903,9 @@ var COMMAND_DOCS = {
179859
179903
  },
179860
179904
  init: {
179861
179905
  usage: "[options]",
179862
- summary: "Use once per project to set up config and agent skills.",
179906
+ summary: "Use once per project to set up config and the agent skill.",
179863
179907
  description: [
179864
- "Create docbridge.config.json and install the DocBridge agent skills.",
179908
+ "Create docbridge.config.json and install the DocBridge agent skill.",
179865
179909
  "Use it once per project, when adopting DocBridge. Run it with --dry-run",
179866
179910
  "first to see what would be written."
179867
179911
  ].join(`
@@ -179878,7 +179922,7 @@ var COMMAND_DOCS = {
179878
179922
  usage: "[options]",
179879
179923
  summary: "Use to let a coding agent decide the setup instead of `init`.",
179880
179924
  description: [
179881
- "Install the docbridge-adopt skill and print the commands that let a coding",
179925
+ "Install the docbridge skill and print the commands that let a coding",
179882
179926
  "agent finish the setup.",
179883
179927
  "Use it instead of `init` when an agent should decide the configuration",
179884
179928
  "rather than accepting the defaults."
@@ -180021,7 +180065,15 @@ function hasHelpFlag(args) {
180021
180065
  }
180022
180066
 
180023
180067
  // src/cli/init.ts
180024
- import { cpSync, mkdirSync as mkdirSync2, readFileSync as readFileSync5, statSync as statSync3, writeFileSync } from "node:fs";
180068
+ import {
180069
+ cpSync,
180070
+ lstatSync as lstatSync3,
180071
+ mkdirSync as mkdirSync2,
180072
+ readFileSync as readFileSync5,
180073
+ rmSync,
180074
+ statSync as statSync3,
180075
+ writeFileSync
180076
+ } from "node:fs";
180025
180077
  import { join as join8, resolve as resolve3 } from "node:path";
180026
180078
 
180027
180079
  // src/core/init-discovery.ts
@@ -180494,15 +180546,31 @@ function executeFileOperation(projectRoot, operation) {
180494
180546
  }
180495
180547
  }
180496
180548
  function executeSkillOperation(projectRoot, packageRoot, operation) {
180549
+ const destinationDir = join8(projectRoot, operation.path);
180550
+ if (operation.action === "remove") {
180551
+ try {
180552
+ if (lstatSync3(destinationDir).isSymbolicLink()) {
180553
+ return;
180554
+ }
180555
+ } catch {
180556
+ return;
180557
+ }
180558
+ rmSync(destinationDir, { recursive: true, force: true });
180559
+ return;
180560
+ }
180497
180561
  if (operation.action !== "create" && operation.action !== "overwrite") {
180498
180562
  return;
180499
180563
  }
180564
+ try {
180565
+ if (lstatSync3(destinationDir).isSymbolicLink()) {
180566
+ return;
180567
+ }
180568
+ } catch {}
180500
180569
  const skillName = operation.path.split("/").at(-1);
180501
180570
  if (skillName === undefined) {
180502
180571
  return;
180503
180572
  }
180504
180573
  const sourceDir = join8(packageRoot, "templates", "skills", skillName);
180505
- const destinationDir = join8(projectRoot, operation.path);
180506
180574
  mkdirSync2(destinationDir, { recursive: true });
180507
180575
  cpSync(sourceDir, destinationDir, { recursive: true, force: true });
180508
180576
  }
@@ -180560,6 +180628,7 @@ function runCheck(options, io) {
180560
180628
 
180561
180629
  ` : "";
180562
180630
  io.stdout(`${body}${formatSummary(result.summary)}
180631
+ See \`docbridge docs show troubleshooting\` for diagnostic codes and fixes.
180563
180632
  `);
180564
180633
  if (result.diagnostics.some((diagnostic) => diagnostic.code === "config_file_invalid")) {
180565
180634
  const guidance = existsSync4(join9(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
@@ -5,22 +5,32 @@ description: Wire DocBridge into coding agents, Git hooks, and CI.
5
5
  # Agent Integration
6
6
 
7
7
  DocBridge gives coding agents deterministic link data instead of asking them
8
- to infer documentation relationships from filenames.
8
+ to infer documentation relationships from filenames. An agent can do the whole
9
+ job from `docbridge --help` plus `docbridge docs list` / `docbridge docs show`.
10
+ A project skill is an accelerator, not a prerequisite.
9
11
 
10
12
  <!-- @code src/cli/init.ts#runInitWithAgent -->
11
13
 
12
- ## Install agent skills
14
+ ## Invoking DocBridge
13
15
 
14
- `docbridge init` can install the distributable skills while creating project
15
- configuration. To let an agent decide the scope, run:
16
+ Run DocBridge with the project's native invocation: `docbridge` on `PATH`, a
17
+ repo recipe such as `just check`, or
18
+ `bun run /path/to/docbridge/src/cli/index.ts`.
19
+
20
+ `docbridge init` can create `docbridge.config.json` and copy the distributable
21
+ `docbridge` skill for Codex, Claude Code, or both. To let an agent decide the
22
+ scope, run:
16
23
 
17
24
  ```sh
18
25
  docbridge init-with-agent --agent-target codex
19
26
  ```
20
27
 
21
28
  Use `claude` or `both` for other supported targets. The command installs the
22
- adoption skill and prints the next command or prompt; it does not launch an
23
- agent process.
29
+ skill and prints the next command or prompt; it does not launch an agent
30
+ process.
31
+
32
+ <!-- @code src/core/context.ts#context -->
33
+ <!-- @code src/core/related.ts#related -->
24
34
 
25
35
  ## Editing workflow
26
36
 
@@ -36,8 +46,17 @@ After editing, inspect the change set:
36
46
  git diff --name-only | docbridge related --stdin --gate
37
47
  ```
38
48
 
39
- For every reported counterpart, update it when behavior diverged or record why
40
- no documentation or code change is necessary.
49
+ For every reported counterpart, choose one of three judgments:
50
+
51
+ - Update the counterpart when behavior, contract, format, or constraint
52
+ diverged.
53
+ - Leave it unchanged, with a written justification that cites the counterpart
54
+ content, when the change is internal and every documented statement still
55
+ holds.
56
+ - Fix the link itself when the annotation pair points at the wrong section or
57
+ symbol.
58
+
59
+ Do not detach a link only to silence the gate.
41
60
 
42
61
  ## Hooks
43
62
 
@@ -68,3 +87,10 @@ Run `docbridge check` over the whole project as the hard link-validity gate. A
68
87
  pull-request workflow can additionally pass the base-to-head changed file list
69
88
  to `related --stdin --gate` and attach `context` output for reviewers. Ensure
70
89
  the checkout includes both commits needed for the diff.
90
+
91
+ ## Read more
92
+
93
+ Run `docbridge docs list` for the full menu of packaged guides, then
94
+ `docbridge docs show <name>` for a page. Start with `getting-started`,
95
+ `annotations`, `linking-workflow`, `link-review`, `troubleshooting`, and
96
+ `commands`.
@@ -7,6 +7,23 @@ description: Write @doc and @code annotations and valid link targets.
7
7
  DocBridge links supported code declarations to Markdown headings. A complete
8
8
  pair has `@doc` on the declaration and `@code` immediately before the heading.
9
9
 
10
+ <!-- @code src/core/links.ts#parseLinkTarget -->
11
+
12
+ ## Target grammar
13
+
14
+ Both `@doc` and `@code` take a `file#fragment` target:
15
+
16
+ - The file path is project-root-relative and uses `/` separators.
17
+ - `./`, `../`, and absolute paths are invalid.
18
+ - The fragment is required. File-only, fragment-only, and same-file targets
19
+ are invalid.
20
+ - Optional text after the target is allowed and ignored
21
+ (`@doc docs/auth.md#login-spec human note`).
22
+
23
+ One declaration may carry multiple `@doc` tags, and one heading may carry
24
+ multiple `@code` comments. Each pair is an independent link. Duplicate
25
+ annotations from the same source to the same target produce `duplicate_link`.
26
+
10
27
  ## Code to documentation
11
28
 
12
29
  Use a documentation path and heading anchor in a declaration's documentation
@@ -19,14 +36,106 @@ comment:
19
36
  export function login(): void {}
20
37
  ```
21
38
 
22
- Swift, Dart, and Rust use their normal documentation comment syntax with the same
23
- target format. Paths are relative to the configured project root.
39
+ ```swift
40
+ /// @doc docs/auth.md#login-flow
41
+ public func login(email: String, password: String) {}
42
+ ```
43
+
44
+ ```dart
45
+ /// @doc docs/auth.md#login-flow
46
+ void login(String email, String password) {}
47
+ ```
48
+
49
+ ```rust
50
+ /// @doc docs/auth.md#login-flow
51
+ pub fn login(email: &str, password: &str) {}
52
+ ```
53
+
54
+ Swift, Dart, and Rust use their normal documentation comment syntax. Paths are
55
+ relative to the configured project root.
56
+
57
+ An `@doc` on an unsupported declaration produces `unsupported_declaration`. The
58
+ same declaration without `@doc` is ignored.
59
+
60
+ ### TypeScript
61
+
62
+ Supported top-level exported forms: `function`, `class`, `abstract class`,
63
+ `interface`, `type`, `enum`, `const enum`, `const` with a single declarator,
64
+ named default `function`/`class`, and their `declare` forms in `.ts` files.
65
+ Type members are also supported: class methods, properties, getters, setters,
66
+ constructors, and static members; interface property and method signatures;
67
+ and property and method signatures of a type alias written as an object type
68
+ literal.
69
+
70
+ By default, `public` and `protected` members are included; `private` members
71
+ are included only when `include.code.typescript.visibility` lists `private`.
72
+ TypeScript `visibility` applies only to type members. Top-level declarations
73
+ are scoped by `export`. Members excluded by visibility are not endpoints; an
74
+ `@doc` on one is `unsupported_declaration`.
75
+
76
+ Unsupported examples include anonymous default exports, multi-declarator
77
+ `const`, namespaces, re-exports, non-exported declarations, members whose name
78
+ is not a plain identifier, enum members, index signatures, call and construct
79
+ signatures, and constructor parameter properties.
80
+
81
+ Canonical IDs omit parameter signatures (`AuthService.login`,
82
+ `AuthService.constructor`).
83
+
84
+ ### Swift
85
+
86
+ Supported forms include top-level and member `class`, `struct`, `enum`,
87
+ `protocol`, and `actor`; top-level and member `func`, `var`, `let`, and
88
+ `init`; and members declared in extensions, canonicalized as members of the
89
+ extended type.
90
+
91
+ By default only `public` and `open` declarations are included. `internal`
92
+ declarations are included when `include.code.swift.visibility` lists
93
+ `internal`. An `@doc` on a visibility-excluded declaration is
94
+ `unsupported_declaration`.
95
+
96
+ Member IDs are type-qualified and include argument labels, for example
97
+ `AuthService.login(email:password:)`, `AuthService.refresh(_:)`, and
98
+ `AuthService.init(email:password:)`.
99
+
100
+ ### Dart
101
+
102
+ Supported forms include top-level functions, getters, setters, and variables;
103
+ `class`, `enum`, `mixin`, and their members; and members declared in
104
+ extensions, canonicalized as members of the extended type.
105
+
106
+ Only public declarations are scanned. Dart marks library-private names with a
107
+ leading `_`, so an endpoint is excluded when any segment of its canonical ID
108
+ starts with `_`, including a public member of a `_Private` type.
109
+ `include.code.dart.visibility` accepts only `public`. An `@doc` on a
110
+ visibility-excluded declaration is `unsupported_declaration`.
111
+
112
+ Member IDs omit parameter signatures (`AuthService.login`). Setters carry a
113
+ trailing `=` (`AuthService.token=`). The unnamed constructor is
114
+ `AuthService.new`; named constructors keep their name (`AuthService.guest`).
115
+
116
+ ### Rust
117
+
118
+ Supported forms are `mod` (including nested modules), `struct`, `enum`, free
119
+ `fn`, and inherent `impl` methods. Trait definitions, trait-impl methods,
120
+ macros, const/static items, unions, and extern blocks are unsupported.
121
+
122
+ By default only unrestricted `pub` declarations are included. A plain `fn` or
123
+ `struct` without `pub` is not an endpoint. Non-`pub` items are included when
124
+ `include.code.rust.visibility` lists `private`. An `@doc` on a
125
+ visibility-excluded declaration is `unsupported_declaration`.
126
+
127
+ Canonical IDs use `::` path qualification, for example `normalize`,
128
+ `TypingEngine`, `TypingEngine::advance`, and `domain::typing`.
24
129
 
25
130
  <!-- @code src/core/markdown.ts#scanMarkdown -->
26
131
 
27
132
  ## Documentation to code
28
133
 
29
- Place an HTML comment immediately before the linked heading:
134
+ Place a standalone HTML comment immediately before the linked heading. The
135
+ comment may be indented by 0 to 3 spaces; 4 or more spaces are ignored. The
136
+ trimmed comment body must start with `@code`. Blank lines between the comment
137
+ and the heading are allowed. Any other text in between produces
138
+ `dangling_code_annotation`.
30
139
 
31
140
  ```md
32
141
  <!-- @code src/auth.ts#login -->
@@ -35,16 +144,28 @@ Place an HTML comment immediately before the linked heading:
35
144
  ```
36
145
 
37
146
  The code fragment is the canonical symbol ID emitted by the language scanner.
38
- Top-level functions and types normally use their declaration name. Members use
39
- their type-qualified identity. TypeScript member IDs omit parameter signatures,
40
- Swift member IDs include argument labels, Dart member IDs omit parameter
41
- signatures, and Rust member IDs use `::` path qualification.
147
+ Use that ID exactly in the `@code` target.
42
148
 
43
149
  ## Anchors and reciprocity
44
150
 
45
- Markdown anchors use the normalized heading text, such as `# Login Flow`
46
- becoming `#login-flow`. Duplicate headings receive the same numeric suffixes
47
- used by GitHub-style Markdown anchors.
151
+ Anchors come from ATX headings (`#` through `######`) only. Setext headings
152
+ have no anchors. Empty headings do not create linkable anchors; a `@code`
153
+ attached to an empty heading is `dangling_code_annotation`.
154
+
155
+ The algorithm:
156
+
157
+ 1. Take the heading text after the `#` markers.
158
+ 2. Lowercase with JavaScript `toLowerCase()`.
159
+ 3. Collapse runs of whitespace and punctuation to `-`. Unicode letters and
160
+ numbers are preserved.
161
+ 4. Strip leading and trailing `-`.
162
+
163
+ Example: `## Login Spec (v2)` becomes `#login-spec-v2`. `# Login Flow`
164
+ becomes `#login-flow`.
165
+
166
+ Duplicate non-empty anchors in the same Markdown file produce
167
+ `duplicate_doc_anchor`. The same anchor in different files is allowed.
168
+ DocBridge does not add GitHub-style numeric suffixes.
48
169
 
49
170
  Each direction is validated independently. A target can resolve while still
50
171
  reporting a missing reciprocal annotation. Run `docbridge check` to validate