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.
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/PKG-INFO +27 -5
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/README.md +13 -6
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/packaging/pypi/README.md +26 -4
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/pyproject.toml +1 -1
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/__init__.py +1 -1
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/cli/__init__.py +40 -0
- ai_docs_toolkit-0.4.0/src/ai_docs_toolkit/core/code_links.py +369 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/docs/agent-installation.md +66 -1
- {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
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/PKG-INFO +27 -5
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/SOURCES.txt +2 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_cli_baseline.py +2 -2
- ai_docs_toolkit-0.4.0/tests/test_code_links.py +349 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/setup.cfg +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/__main__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/context/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/context/markdown_output.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/azure_devops_provider.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/changed_files.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/config.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/context.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/doctor.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/file_lookup.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/frontmatter.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/graph.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/impact.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/init.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/planning_operations.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/planning_scaffold.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/registry.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/requirement_capture.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/scanner.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/schema_validation.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/section_fix.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/structure_validation.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/core/task_export.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/graph/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/graph/json_output.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/impact/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/impact/json_output.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/__main__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/server.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/mcp/setup.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/check.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/review/record.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/AGENTS.md +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/ai-docs-index.md +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/templates/project/ai-docs.config.yaml +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/validation/__init__.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit/validation/human.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/dependency_links.txt +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/entry_points.txt +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/requires.txt +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/src/ai_docs_toolkit.egg-info/top_level.txt +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_azure_devops_provider.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_bootstrap_templates.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_config_loader_and_scanner.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_consumer_matrix.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_cli_json.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_cli_markdown.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_depth.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_policy.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_context_selector.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_document_graph.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_document_registry.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_execution_evals.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_frontmatter_parser.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_graph_cli_json.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_analysis.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_cli_human.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_impact_cli_json.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_init_cli.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_mcp_server.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_review_check.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_schema_validation.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_skill_eval_harness.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_structure_validation.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_task_export.py +0 -0
- {ai_docs_toolkit-0.3.3 → ai_docs_toolkit-0.4.0}/tests/test_validate_cli_human.py +0 -0
- {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
|
+
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
87
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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)
|