ai-docs-toolkit 0.3.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/PKG-INFO +27 -5
  2. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/README.md +13 -6
  3. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/packaging/pypi/README.md +26 -4
  4. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/pyproject.toml +1 -1
  5. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/__init__.py +1 -1
  6. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/cli/__init__.py +40 -0
  7. ai_docs_toolkit-0.4.0/src/ai_docs_toolkit/core/code_links.py +369 -0
  8. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/docs/agent-installation.md +66 -1
  9. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/documentation-cascade-authoring-skill.md +61 -3
  10. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/PKG-INFO +27 -5
  11. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/SOURCES.txt +2 -0
  12. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_cli_baseline.py +2 -2
  13. ai_docs_toolkit-0.4.0/tests/test_code_links.py +349 -0
  14. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/setup.cfg +0 -0
  15. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/__main__.py +0 -0
  16. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/context/__init__.py +0 -0
  17. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/context/markdown_output.py +0 -0
  18. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/__init__.py +0 -0
  19. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/azure_devops_provider.py +0 -0
  20. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/changed_files.py +0 -0
  21. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/config.py +0 -0
  22. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/context.py +0 -0
  23. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/doctor.py +0 -0
  24. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/file_lookup.py +0 -0
  25. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/frontmatter.py +0 -0
  26. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/graph.py +0 -0
  27. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/impact.py +0 -0
  28. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/init.py +0 -0
  29. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/planning_operations.py +0 -0
  30. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/planning_scaffold.py +0 -0
  31. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/registry.py +0 -0
  32. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/requirement_capture.py +0 -0
  33. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/scanner.py +0 -0
  34. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/schema_validation.py +0 -0
  35. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/section_fix.py +0 -0
  36. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/structure_validation.py +0 -0
  37. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/task_export.py +0 -0
  38. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/graph/__init__.py +0 -0
  39. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/graph/json_output.py +0 -0
  40. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/impact/__init__.py +0 -0
  41. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/impact/json_output.py +0 -0
  42. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/__init__.py +0 -0
  43. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/__main__.py +0 -0
  44. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/server.py +0 -0
  45. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/setup.py +0 -0
  46. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/__init__.py +0 -0
  47. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/check.py +0 -0
  48. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/record.py +0 -0
  49. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/examples/skills/release-publication/SKILL.md +0 -0
  50. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/documentation-change-review-skill.md +0 -0
  51. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/incoming-request-model.md +0 -0
  52. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/incoming-request-triage-skill.md +0 -0
  53. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/init-command-contract.md +0 -0
  54. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/planning-document-model.md +0 -0
  55. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/reference/toolkit-maintenance-context-routing.md +0 -0
  56. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/acceptance.schema.yaml +0 -0
  57. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/architecture-decision.schema.yaml +0 -0
  58. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/asset.schema.yaml +0 -0
  59. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/business-rule.schema.yaml +0 -0
  60. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/contract.schema.yaml +0 -0
  61. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/guide.schema.yaml +0 -0
  62. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/implementation-task.schema.yaml +0 -0
  63. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/index.schema.yaml +0 -0
  64. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/inventory.schema.yaml +0 -0
  65. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/module.schema.yaml +0 -0
  66. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/operation.schema.yaml +0 -0
  67. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/plan-stage.schema.yaml +0 -0
  68. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/plan.schema.yaml +0 -0
  69. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/runbook.schema.yaml +0 -0
  70. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/scenario.schema.yaml +0 -0
  71. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/standalone-task.schema.yaml +0 -0
  72. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/task-registry.schema.yaml +0 -0
  73. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/test-case.schema.yaml +0 -0
  74. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/schemas/frontmatter.schema.yaml +0 -0
  75. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/documentation-cascade-authoring/SKILL.md +0 -0
  76. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/documentation-change-review/SKILL.md +0 -0
  77. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/incoming-request-triage/SKILL.md +0 -0
  78. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/index.md +0 -0
  79. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/planning-record-generation/SKILL.md +0 -0
  80. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/planning-task-execution/SKILL.md +0 -0
  81. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/toolkit-consumer-policy/SKILL.md +0 -0
  82. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/skills/update-ai-docs-toolkit/SKILL.md +0 -0
  83. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/agent-context-architecture-beta-18.md +0 -0
  84. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/archive-validation-beta-12.md +0 -0
  85. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/cascade-impact-beta-14.md +0 -0
  86. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/consumer-migration-hardening.md +0 -0
  87. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/decision-visibility-beta-13.md +0 -0
  88. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/doctor-filesystem-traversal-beta-19.md +0 -0
  89. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/doctor-json-provenance-beta-20.md +0 -0
  90. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/documentation-cascade-authoring-beta-23.md +0 -0
  91. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/documentation-change-review-beta-21.md +0 -0
  92. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/incoming-request-storage-beta-17.md +0 -0
  93. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/incoming-request-triage-beta-22.md +0 -0
  94. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/migration-note.md +0 -0
  95. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/post-publication-checks.md +0 -0
  96. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/release-task.md +0 -0
  97. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/routing-restoration-beta-15.md +0 -0
  98. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/task-retirement-beta-16.md +0 -0
  99. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/validation-evidence.md +0 -0
  100. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/templates/release/version-change-checklist.md +0 -0
  101. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/workflows/documentation-cascade-adoption.md +0 -0
  102. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/workflows/plan-stage-workflow.md +0 -0
  103. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/.ai-docs/workflows/toolkit-migration-workflow.md +0 -0
  104. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/AGENTS.md +0 -0
  105. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/ai-docs-index.md +0 -0
  106. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/ai-docs.config.yaml +0 -0
  107. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/validation/__init__.py +0 -0
  108. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/validation/human.py +0 -0
  109. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/dependency_links.txt +0 -0
  110. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/entry_points.txt +0 -0
  111. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/requires.txt +0 -0
  112. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/top_level.txt +0 -0
  113. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_azure_devops_provider.py +0 -0
  114. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_bootstrap_templates.py +0 -0
  115. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_config_loader_and_scanner.py +0 -0
  116. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_consumer_matrix.py +0 -0
  117. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_cli_json.py +0 -0
  118. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_cli_markdown.py +0 -0
  119. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_depth.py +0 -0
  120. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_policy.py +0 -0
  121. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_selector.py +0 -0
  122. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_document_graph.py +0 -0
  123. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_document_registry.py +0 -0
  124. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_execution_evals.py +0 -0
  125. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_frontmatter_parser.py +0 -0
  126. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_graph_cli_json.py +0 -0
  127. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_analysis.py +0 -0
  128. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_cli_human.py +0 -0
  129. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_cli_json.py +0 -0
  130. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_init_cli.py +0 -0
  131. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_mcp_server.py +0 -0
  132. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_review_check.py +0 -0
  133. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_schema_validation.py +0 -0
  134. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_skill_eval_harness.py +0 -0
  135. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_structure_validation.py +0 -0
  136. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_task_export.py +0 -0
  137. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_validate_cli_human.py +0 -0
  138. {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_validate_cli_json.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-docs-toolkit
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: AI-oriented documentation toolkit.
5
5
  Requires-Python: >=3.14
6
6
  Description-Content-Type: text/markdown
@@ -30,13 +30,14 @@ The core idea is that documentation should not be a secondary artifact created a
30
30
 
31
31
  ## Current Status
32
32
 
33
- Version 0.3.3 includes:
33
+ Version 0.4.0 includes:
34
34
 
35
35
  - schema and structure validation for Markdown documents with YAML front matter;
36
36
  - document graph output;
37
37
  - impact analysis with linked test cases and changed-path discovery coverage;
38
38
  - optional saved review evidence and the read-only `ai-docs review-check` command;
39
39
  - an optional convention for documentation links beside key code;
40
+ - the opt-in `ai-docs code-links-check` command for Python comment paths and primary IDs;
40
41
  - test-evidence review connecting requirements to assertions and proportionate sensitivity checks;
41
42
  - context bundle output;
42
43
  - an executable `ai-docs` CLI;
@@ -47,26 +48,41 @@ Version 0.3.3 includes:
47
48
  Install this version with Python 3.14 or newer once it is available on PyPI:
48
49
 
49
50
  ```bash
50
- python -m pip install ai-docs-toolkit==0.3.3
51
+ python -m pip install ai-docs-toolkit==0.4.0
51
52
  ai-docs --version
52
53
  ```
53
54
 
54
55
  For ephemeral execution with `uvx`:
55
56
 
56
57
  ```bash
57
- uvx --python 3.14 --from ai-docs-toolkit==0.3.3 ai-docs --version
58
+ uvx --python 3.14 --from ai-docs-toolkit==0.4.0 ai-docs --version
58
59
  ```
59
60
 
60
61
  For the optional local MCP runtime:
61
62
 
62
63
  ```bash
63
- python -m pip install "ai-docs-toolkit[mcp]==0.3.3"
64
+ python -m pip install "ai-docs-toolkit[mcp]==0.4.0"
64
65
  ai-docs-mcp --project-root .
65
66
  ```
66
67
 
67
68
  For the latest stable release, use `python -m pip install --upgrade ai-docs-toolkit`
68
69
  without an exact version pin.
69
70
 
71
+ ## Release 0.4.0
72
+
73
+ `ai-docs code-links-check FILE... --format json` checks documentation paths and
74
+ primary IDs in real Python comments without importing code or changing files.
75
+ Explicit files, repository boundaries and unverified inputs remain visible;
76
+ strings/docstrings are ignored and links remain optional. Semantic agreement
77
+ between documentation, comments, implementation and tests still requires review.
78
+ The command does not add a gate to ordinary validation or new graph edges.
79
+
80
+ After upgrading from 0.3.3, inspect `init --check` and `--dry-run`, then refresh the
81
+ existing authoring reference with `init`. Preserve local rules, project documents
82
+ and review history; no schema or bulk annotation migration is required. Minimal
83
+ profile needs no reference refresh. See “Migration 0.3.3 To 0.4.0” in the packaged
84
+ `ai_docs_toolkit/docs/agent-installation.md` for commands, checks and rollback.
85
+
70
86
  ## Release 0.3.3
71
87
 
72
88
  Documentation change review now traces material requirements to test inputs and
@@ -305,6 +321,12 @@ Check optional saved review evidence:
305
321
  ai-docs review-check review.yaml --format json
306
322
  ```
307
323
 
324
+ Check existing documentation links in selected Python files:
325
+
326
+ ```bash
327
+ ai-docs code-links-check src/example.py --format json
328
+ ```
329
+
308
330
  Prepare an agent context bundle for changed files:
309
331
 
310
332
  ```bash
@@ -2,7 +2,7 @@
2
2
 
3
3
  AI Docs Toolkit is a documentation toolkit for AI-assisted software development.
4
4
 
5
- It treats documentation as an engineering interface between humans, AI agents, code, tests and change management. Source version 0.3.3 refines test-evidence review, alongside optional code documentation links, rollback guidance, linked test cases, changed-path coverage and optional review evidence checks. Python 3.14 or newer is required.
5
+ It treats documentation as an engineering interface between humans, AI agents, code, tests and change management. Version 0.4.0 adds an optional checker for documentation IDs and paths in Python comments, alongside test-evidence review, rollback guidance, linked test cases, changed-path coverage and optional review evidence checks. Python 3.14 or newer is required.
6
6
 
7
7
  ## Purpose
8
8
 
@@ -83,9 +83,8 @@ pipx install ai-docs-toolkit
83
83
  uvx --from ai-docs-toolkit ai-docs validate
84
84
  ```
85
85
 
86
- For upgrades from 0.3.2 to 0.3.3, follow the [consumer migration guide](src/ai_docs_toolkit/docs/agent-installation.md#migration-032-to-033).
87
- Install the exact version only after it is available on PyPI; a source preparation
88
- commit does not publish the package. Consumers on 0.3.1 must also read the
86
+ For upgrades from 0.3.3 to 0.4.0, follow the [consumer migration guide](src/ai_docs_toolkit/docs/agent-installation.md#migration-033-to-040).
87
+ Consumers on 0.3.1 must also read the
89
88
  [0.3.2 migration](src/ai_docs_toolkit/docs/agent-installation.md#migration-031-to-032).
90
89
  Consumers on 0.3.0 must also read the
91
90
  [0.3.1 migration](src/ai_docs_toolkit/docs/agent-installation.md#migration-030-to-031).
@@ -97,8 +96,7 @@ Use Python 3.14+, review managed-file changes and preserve existing project rule
97
96
  and task records before refreshing. Completed standalone tasks are archived;
98
97
  tasks in a plan remain in place until the whole plan is archived.
99
98
 
100
- Version 0.3.3 is prepared in source and awaits publication approval; the latest
101
- published version remains 0.3.2. The
99
+ Version 0.4.0 is prepared for publication; [0.3.3 remains available on Public PyPI](https://pypi.org/project/ai-docs-toolkit/0.3.3/). The
102
100
  [test-evidence review rule](.ai-docs/reference/documentation-change-review-skill.md#test-evidence-review)
103
101
  connects material requirements to test setup and assertions. A concrete risk of
104
102
  masked violations calls for a proportionate check within existing authority.
@@ -181,6 +179,15 @@ impact candidates. It does not approve semantic correctness. See the
181
179
  [review record contract](.ai-docs/reference/documentation-change-review-skill.md#advisory-checker)
182
180
  for YAML/Markdown input, tool identity and exit codes.
183
181
 
182
+ Version 0.4.0 provides an optional check for existing Python code links:
183
+
184
+ ```bash
185
+ ai-docs code-links-check src/example.py --format json
186
+ ```
187
+
188
+ Run from the Git root with explicit files. This checks paths and primary IDs,
189
+ without importing code or assessing semantic agreement. See the [code-link contract](.ai-docs/reference/documentation-cascade-authoring-skill.md#code-link-checker).
190
+
184
191
  Prepare an agent context bundle for changed files:
185
192
 
186
193
  ```bash
@@ -16,13 +16,14 @@ The core idea is that documentation should not be a secondary artifact created a
16
16
 
17
17
  ## Current Status
18
18
 
19
- Version 0.3.3 includes:
19
+ Version 0.4.0 includes:
20
20
 
21
21
  - schema and structure validation for Markdown documents with YAML front matter;
22
22
  - document graph output;
23
23
  - impact analysis with linked test cases and changed-path discovery coverage;
24
24
  - optional saved review evidence and the read-only `ai-docs review-check` command;
25
25
  - an optional convention for documentation links beside key code;
26
+ - the opt-in `ai-docs code-links-check` command for Python comment paths and primary IDs;
26
27
  - test-evidence review connecting requirements to assertions and proportionate sensitivity checks;
27
28
  - context bundle output;
28
29
  - an executable `ai-docs` CLI;
@@ -33,26 +34,41 @@ Version 0.3.3 includes:
33
34
  Install this version with Python 3.14 or newer once it is available on PyPI:
34
35
 
35
36
  ```bash
36
- python -m pip install ai-docs-toolkit==0.3.3
37
+ python -m pip install ai-docs-toolkit==0.4.0
37
38
  ai-docs --version
38
39
  ```
39
40
 
40
41
  For ephemeral execution with `uvx`:
41
42
 
42
43
  ```bash
43
- uvx --python 3.14 --from ai-docs-toolkit==0.3.3 ai-docs --version
44
+ uvx --python 3.14 --from ai-docs-toolkit==0.4.0 ai-docs --version
44
45
  ```
45
46
 
46
47
  For the optional local MCP runtime:
47
48
 
48
49
  ```bash
49
- python -m pip install "ai-docs-toolkit[mcp]==0.3.3"
50
+ python -m pip install "ai-docs-toolkit[mcp]==0.4.0"
50
51
  ai-docs-mcp --project-root .
51
52
  ```
52
53
 
53
54
  For the latest stable release, use `python -m pip install --upgrade ai-docs-toolkit`
54
55
  without an exact version pin.
55
56
 
57
+ ## Release 0.4.0
58
+
59
+ `ai-docs code-links-check FILE... --format json` checks documentation paths and
60
+ primary IDs in real Python comments without importing code or changing files.
61
+ Explicit files, repository boundaries and unverified inputs remain visible;
62
+ strings/docstrings are ignored and links remain optional. Semantic agreement
63
+ between documentation, comments, implementation and tests still requires review.
64
+ The command does not add a gate to ordinary validation or new graph edges.
65
+
66
+ After upgrading from 0.3.3, inspect `init --check` and `--dry-run`, then refresh the
67
+ existing authoring reference with `init`. Preserve local rules, project documents
68
+ and review history; no schema or bulk annotation migration is required. Minimal
69
+ profile needs no reference refresh. See “Migration 0.3.3 To 0.4.0” in the packaged
70
+ `ai_docs_toolkit/docs/agent-installation.md` for commands, checks and rollback.
71
+
56
72
  ## Release 0.3.3
57
73
 
58
74
  Documentation change review now traces material requirements to test inputs and
@@ -291,6 +307,12 @@ Check optional saved review evidence:
291
307
  ai-docs review-check review.yaml --format json
292
308
  ```
293
309
 
310
+ Check existing documentation links in selected Python files:
311
+
312
+ ```bash
313
+ ai-docs code-links-check src/example.py --format json
314
+ ```
315
+
294
316
  Prepare an agent context bundle for changed files:
295
317
 
296
318
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-docs-toolkit"
7
- version = "0.3.3"
7
+ version = "0.4.0"
8
8
  description = "AI-oriented documentation toolkit."
9
9
  readme = "packaging/pypi/README.md"
10
10
  requires-python = ">=3.14"
@@ -1,3 +1,3 @@
1
1
  """AI Docs Toolkit package baseline."""
2
2
 
3
- __version__ = "0.3.3"
3
+ __version__ = "0.4.0"
@@ -91,6 +91,7 @@ def _help_text() -> str:
91
91
  " ai-docs tasks push --provider azure-devops (--task TASK-ID | --stage STAGE-ID | --plan PLAN-ID) --organization ORG --project PROJECT [--dry-run] [--write-back] [--format json]",
92
92
  " ai-docs graph --format json [--type TYPE] [--module MODULE] [--id ID]",
93
93
  " ai-docs impact (--id ID | --changed [--base REF]) [--format json]",
94
+ " ai-docs code-links-check FILE... [--format human|json|--json]",
94
95
  " ai-docs review-check RECORD [--record N] [--format json|--json]",
95
96
  " ai-docs context (--id ID | --module MODULE | --feature FEATURE | --changed) [--format json] [--history exclude|include|legacy] [--body-budget-bytes N] [--depth N]",
96
97
  " ai-docs mcp status [--project-root PATH] [--config FILE]",
@@ -117,6 +118,21 @@ def main(argv: list[str] | None = None) -> int:
117
118
  print(__version__)
118
119
  return 0
119
120
 
121
+ if args[0] == "code-links-check":
122
+ import json
123
+ import sys
124
+ from ai_docs_toolkit.core.code_links import check_code_links, format_code_links_human
125
+
126
+ parsed = _parse_code_links_args(args[1:])
127
+ if parsed is None:
128
+ print("Usage: ai-docs code-links-check FILE... [--format human|json|--json]", file=sys.stderr)
129
+ return 3
130
+ paths, output_format = parsed
131
+ result = check_code_links(paths, ".")
132
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True)
133
+ if output_format == "json" else format_code_links_human(result))
134
+ return result["exit_code"]
135
+
120
136
  if args[0] == "review-check":
121
137
  import json
122
138
  import sys
@@ -1194,3 +1210,27 @@ def _parse_review_args(args):
1194
1210
  return None
1195
1211
  number = int(number)
1196
1212
  return args[1], number, 'json' if '--format' in options or '--json' in options else 'human'
1213
+
1214
+
1215
+ def _parse_code_links_args(args: list[str]) -> tuple[list[str], str] | None:
1216
+ paths = []
1217
+ output_format = None
1218
+ index = 0
1219
+ while index < len(args):
1220
+ item = args[index]
1221
+ if item in ("--json", "--format"):
1222
+ if output_format is not None:
1223
+ return None
1224
+ if item == "--json":
1225
+ output_format = "json"
1226
+ else:
1227
+ index += 1
1228
+ if index >= len(args) or args[index] not in ("human", "json"):
1229
+ return None
1230
+ output_format = args[index]
1231
+ elif item.startswith("-"):
1232
+ return None
1233
+ else:
1234
+ paths.append(item)
1235
+ index += 1
1236
+ return (paths, output_format or "human") if paths else None
@@ -0,0 +1,369 @@
1
+ """Opt-in, read-only checks for documentation links in Python comments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import os
7
+ from pathlib import Path, PurePosixPath, PureWindowsPath
8
+ import re
9
+ import stat
10
+ import subprocess
11
+ import tokenize
12
+ from typing import Any
13
+
14
+ import yaml
15
+
16
+ from ai_docs_toolkit.core.config import load_project_config
17
+ from ai_docs_toolkit.core.frontmatter import parse_markdown_text
18
+ from ai_docs_toolkit.core.registry import (
19
+ build_document_registry,
20
+ registry_document_from_parsed,
21
+ )
22
+
23
+ LIMITATIONS = [
24
+ "Only explicitly selected Python files are checked; no full source coverage is claimed.",
25
+ "Paths and primary IDs do not prove semantic consistency, implementation or test completeness.",
26
+ "Run against a stable tree; the scan is not an atomic snapshot or a concurrent-filesystem sandbox.",
27
+ ]
28
+ _MARKER = re.compile(r"^#\s*Docs:")
29
+ _LINK = re.compile(r"^#\s*Docs:\s*([^\s|]+)\s*\|\s*([^|]+?)\s*$")
30
+
31
+
32
+ class _UniqueKeys(yaml.SafeLoader):
33
+ pass
34
+
35
+
36
+ def _mapping(loader, node, deep=False):
37
+ loader.flatten_mapping(node)
38
+ result = {}
39
+ for key_node, value_node in node.value:
40
+ key = loader.construct_object(key_node, deep=deep)
41
+ if key in result:
42
+ raise ValueError("Duplicate YAML mapping key.")
43
+ result[key] = loader.construct_object(value_node, deep=deep)
44
+ return result
45
+
46
+
47
+ _UniqueKeys.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _mapping)
48
+
49
+
50
+ class _UnsupportedPath(ValueError):
51
+ pass
52
+
53
+
54
+ def _local_path(root: Path, value: str) -> Path:
55
+ if (
56
+ not value
57
+ or any(ord(c) < 32 or ord(c) == 127 for c in value)
58
+ or "\\" in value
59
+ or PureWindowsPath(value).drive
60
+ or PurePosixPath(value).is_absolute()
61
+ or any(p in ("", ".", "..", ".git") for p in value.split("/"))
62
+ ):
63
+ raise _UnsupportedPath("Expected a canonical repository-relative POSIX path.")
64
+ current = root
65
+ for part in value.split("/"):
66
+ current = current / part
67
+ if current.is_symlink():
68
+ raise _UnsupportedPath("Symlink traversal is unsupported.")
69
+ if current.is_dir() and (
70
+ (current / ".git").exists() or (current / ".git").is_symlink()
71
+ ):
72
+ raise _UnsupportedPath("Path belongs to a nested Git repository.")
73
+ return current
74
+
75
+
76
+ def _regular(path: Path) -> bool:
77
+ return stat.S_ISREG(path.stat().st_mode)
78
+
79
+
80
+ # Check navigation only; semantic agreement remains a separate review.
81
+ # Docs: REF-DOCUMENTATION-CASCADE-AUTHORING-SKILL-001 | .ai-docs/reference/documentation-cascade-authoring-skill.md
82
+ def check_code_links(
83
+ paths: list[str], project_root: str | Path = "."
84
+ ) -> dict[str, Any]:
85
+ root = Path(project_root).resolve()
86
+ requested = sorted(set(paths))
87
+ observed: dict[str, Any] = dict(
88
+ requested_files=requested,
89
+ checked_files=[],
90
+ unverified_files=[],
91
+ documents_scanned=0,
92
+ links_found=0,
93
+ links=[],
94
+ )
95
+ findings: list[dict[str, Any]] = []
96
+ unavailable = False
97
+
98
+ def add(rule, message, path=None, line=None, document_id=None, target=None):
99
+ findings.append(
100
+ dict(
101
+ rule_id=rule,
102
+ severity="error",
103
+ message=message,
104
+ path=path,
105
+ line=line,
106
+ document_id=document_id,
107
+ target=target,
108
+ )
109
+ )
110
+
111
+ def finish(status=None):
112
+ observed["unverified_files"] = sorted(
113
+ set(requested) - set(observed["checked_files"])
114
+ )
115
+ ordered = sorted(
116
+ findings,
117
+ key=lambda f: (
118
+ f["path"] or "",
119
+ f["line"] or 0,
120
+ f["rule_id"],
121
+ f["document_id"] or "",
122
+ f["message"],
123
+ ),
124
+ )
125
+ status = status or (
126
+ "unavailable" if unavailable else "findings" if ordered else "checked"
127
+ )
128
+ code = {
129
+ "checked": 0,
130
+ "findings": 1,
131
+ "invalid_config": 2,
132
+ "usage_error": 3,
133
+ "unavailable": 4,
134
+ }[status]
135
+ observed["links_found"] = len(observed["links"])
136
+ return dict(
137
+ ok=code == 0,
138
+ status=status,
139
+ exit_code=code,
140
+ findings=ordered,
141
+ observed=observed,
142
+ limitations=LIMITATIONS.copy(),
143
+ )
144
+
145
+ if not requested:
146
+ add("code_link.arguments_invalid", "Supply at least one Python file.")
147
+ return finish("usage_error")
148
+ try:
149
+ git = subprocess.run(
150
+ ["git", "-C", str(root), "rev-parse", "--show-toplevel"],
151
+ env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=10,
155
+ )
156
+ except OSError, UnicodeError, subprocess.TimeoutExpired:
157
+ add("code_link.git_unavailable", "Git repository root could not be determined.")
158
+ return finish("unavailable")
159
+ if git.returncode or Path(git.stdout.rstrip("\n")).resolve() != root:
160
+ add("code_link.repository_root", "Run from the root of the Git repository.")
161
+ return finish("invalid_config")
162
+ try:
163
+ config_path = _local_path(root, "ai-docs.config.yaml")
164
+ if config_path.exists() and not _regular(config_path):
165
+ raise _UnsupportedPath("Configuration is not a regular file.")
166
+ # Validate original paths before the config loader resolves away symlinks.
167
+ raw_config = (
168
+ yaml.safe_load(config_path.read_bytes().decode("utf-8"))
169
+ if config_path.exists()
170
+ else {}
171
+ )
172
+ project = (
173
+ raw_config.get("project", {}) if isinstance(raw_config, dict) else None
174
+ )
175
+ roots = (
176
+ project.get("documentation_roots", ["ai-docs"])
177
+ if isinstance(project, dict)
178
+ else None
179
+ )
180
+ if isinstance(roots, list):
181
+ for value in roots:
182
+ if isinstance(value, str) and value != ".":
183
+ _local_path(root, value)
184
+ loaded = load_project_config(root)
185
+ except yaml.YAMLError:
186
+ add(
187
+ "code_link.config_invalid",
188
+ "Invalid YAML configuration.",
189
+ "ai-docs.config.yaml",
190
+ )
191
+ return finish("invalid_config")
192
+ except (OSError, ValueError, UnicodeError, RecursionError) as exc:
193
+ add("code_link.config_unavailable", str(exc), "ai-docs.config.yaml")
194
+ return finish("unavailable")
195
+ if not loaded.ok:
196
+ for error in loaded.errors:
197
+ add("code_link.config_invalid", error.message, "ai-docs.config.yaml")
198
+ return finish("invalid_config")
199
+ config = loaded.config
200
+ assert config is not None
201
+
202
+ # Inventory without following symlinks; reuse primary/alias identity rules.
203
+ inventory: set[Path] = set()
204
+ visited: set[Path] = set()
205
+
206
+ def walk(directory: Path):
207
+ nonlocal unavailable
208
+ label = directory.relative_to(root).as_posix()
209
+ try:
210
+ if directory != root:
211
+ _local_path(root, label)
212
+ if directory in visited:
213
+ return
214
+ visited.add(directory)
215
+ for child in sorted(directory.iterdir()):
216
+ if child.name in config.excluded_directories:
217
+ continue
218
+ name = child.relative_to(root).as_posix()
219
+ try:
220
+ safe = _local_path(root, name)
221
+ if safe.is_dir():
222
+ walk(safe)
223
+ elif safe.suffix.lower() == ".md":
224
+ if not _regular(safe):
225
+ raise _UnsupportedPath(
226
+ "Markdown inventory entry is not a regular file."
227
+ )
228
+ inventory.add(safe)
229
+ except (OSError, ValueError) as exc:
230
+ unavailable = True
231
+ add("code_link.inventory_unavailable", str(exc), name)
232
+ except (OSError, ValueError) as exc:
233
+ unavailable = True
234
+ add("code_link.inventory_unavailable", str(exc), label)
235
+
236
+ for directory in config.documentation_roots:
237
+ try:
238
+ label = directory.relative_to(root).as_posix()
239
+ if directory != root:
240
+ _local_path(root, label)
241
+ except (OSError, ValueError) as exc:
242
+ unavailable = True
243
+ add("code_link.inventory_unavailable", str(exc))
244
+ continue
245
+ walk(directory)
246
+
247
+ documents = []
248
+ observed["documents_scanned"] = len(inventory)
249
+ for path in sorted(inventory):
250
+ label = path.relative_to(root).as_posix()
251
+ try:
252
+ text = path.read_bytes().decode("utf-8")
253
+ except (OSError, UnicodeError) as exc:
254
+ unavailable = True
255
+ add("code_link.document_unavailable", str(exc), label)
256
+ continue
257
+ try:
258
+ parsed = parse_markdown_text(text, path=str(path))
259
+ except RecursionError, ValueError, TypeError:
260
+ add("code_link.document_invalid", "Front matter cannot be parsed.", label)
261
+ continue
262
+ if not parsed.ok:
263
+ for error in parsed.errors:
264
+ add("code_link.document_invalid", error.message, label, error.line)
265
+ continue
266
+ assert parsed.document is not None
267
+ try:
268
+ yaml.load(parsed.document.raw_frontmatter, Loader=_UniqueKeys)
269
+ except yaml.YAMLError, ValueError, TypeError, RecursionError:
270
+ add(
271
+ "code_link.document_invalid",
272
+ "Front matter has invalid or duplicate mapping keys.",
273
+ label,
274
+ )
275
+ continue
276
+ documents.append(registry_document_from_parsed(parsed.document))
277
+ registry = build_document_registry(documents)
278
+ for issue in registry.issues:
279
+ for path in issue.paths:
280
+ add(
281
+ issue.rule_id,
282
+ issue.message,
283
+ path.relative_to(root).as_posix(),
284
+ document_id=issue.document_id,
285
+ )
286
+
287
+ for name in requested:
288
+ try:
289
+ source = _local_path(root, name)
290
+ if source.suffix != ".py" or not _regular(source):
291
+ raise _UnsupportedPath("Source must be a regular .py file.")
292
+ tokens = list(tokenize.tokenize(io.BytesIO(source.read_bytes()).readline))
293
+ if any(
294
+ t.type == tokenize.ERRORTOKEN and not t.string.isspace() for t in tokens
295
+ ):
296
+ raise ValueError("Invalid Python token.")
297
+ except (
298
+ OSError,
299
+ ValueError,
300
+ SyntaxError,
301
+ LookupError,
302
+ tokenize.TokenError,
303
+ ) as exc:
304
+ unavailable = True
305
+ observed["unverified_files"].append(name)
306
+ add("code_link.source_unavailable", str(exc), name)
307
+ continue
308
+ observed["checked_files"].append(name)
309
+ for token in tokens:
310
+ if token.type != tokenize.COMMENT or not _MARKER.match(token.string):
311
+ continue
312
+ match = _LINK.fullmatch(token.string)
313
+ if not match or not match[2].strip():
314
+ add(
315
+ "code_link.marker_invalid",
316
+ "Expected Docs: <primary ID> | <repository-relative path>.",
317
+ name,
318
+ token.start[0],
319
+ )
320
+ continue
321
+ document_id, target = match[1], match[2].strip()
322
+ link = dict(
323
+ path=name, line=token.start[0], document_id=document_id, target=target
324
+ )
325
+ observed["links"].append(link)
326
+ try:
327
+ target_path = _local_path(root, target)
328
+ if not target_path.exists():
329
+ add(
330
+ "code_link.target_missing",
331
+ "Documentation target does not exist.",
332
+ **link,
333
+ )
334
+ continue
335
+ if target_path not in inventory:
336
+ raise _UnsupportedPath(
337
+ "Target is not a Markdown file in the configured inventory."
338
+ )
339
+ except (OSError, ValueError) as exc:
340
+ unavailable = True
341
+ add("code_link.target_unsupported", str(exc), **link)
342
+ continue
343
+ document = registry.get_by_path(target_path)
344
+ if document is None:
345
+ # Parsing/read findings already identify why this target is unverified.
346
+ continue
347
+ if document.document_id != document_id:
348
+ add(
349
+ "code_link.id_mismatch",
350
+ f"Target primary ID is {document.document_id!r}; comment has {document_id!r}.",
351
+ **link,
352
+ )
353
+ return finish()
354
+
355
+
356
+ def format_code_links_human(result: dict[str, Any]) -> str:
357
+ observed = result["observed"]
358
+ lines = [
359
+ f"Code links: {result['status']} (exit {result['exit_code']})",
360
+ f"Files checked: {len(observed['checked_files'])}/{len(observed['requested_files'])}; "
361
+ f"links: {observed['links_found']}; unverified files: {len(observed['unverified_files'])}",
362
+ ]
363
+ for finding in result["findings"]:
364
+ location = finding["path"] or "(project)"
365
+ if finding["line"] is not None:
366
+ location += f":{finding['line']}"
367
+ lines.append(f"- {location} {finding['rule_id']}: {finding['message']}")
368
+ lines.extend(["Limitations:", *("- " + text for text in result["limitations"])])
369
+ return "\n".join(lines)