docs-drift 0.1.0__py3-none-any.whl
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.
- docs_drift/__init__.py +7 -0
- docs_drift/analysis.py +52 -0
- docs_drift/baseline.py +70 -0
- docs_drift/cli.py +871 -0
- docs_drift/collaboration.py +282 -0
- docs_drift/config.py +113 -0
- docs_drift/docs/__init__.py +1 -0
- docs_drift/docs/defaults.py +250 -0
- docs_drift/docs/discover.py +172 -0
- docs_drift/docs/extract_blocks.py +596 -0
- docs_drift/docs/extract_inline.py +561 -0
- docs_drift/docs/pipeline.py +234 -0
- docs_drift/docs/preprocess.py +71 -0
- docs_drift/docs/sections.py +247 -0
- docs_drift/errors.py +119 -0
- docs_drift/fix/__init__.py +1 -0
- docs_drift/fix/edits.py +124 -0
- docs_drift/fix/model.py +139 -0
- docs_drift/fix/rules.py +232 -0
- docs_drift/git_safety.py +35 -0
- docs_drift/graph/queries/evidence_path.cypher +6 -0
- docs_drift/history.py +412 -0
- docs_drift/history_cache.py +62 -0
- docs_drift/ids.py +50 -0
- docs_drift/index/__init__.py +211 -0
- docs_drift/index/base.py +239 -0
- docs_drift/index/config.py +185 -0
- docs_drift/index/js_cli.py +211 -0
- docs_drift/index/js_routes.py +68 -0
- docs_drift/index/js_symbols.py +219 -0
- docs_drift/index/python_cli.py +370 -0
- docs_drift/index/python_routes.py +102 -0
- docs_drift/index/python_source.py +20 -0
- docs_drift/index/python_symbols.py +335 -0
- docs_drift/index/queries/javascript.scm +4 -0
- docs_drift/index/queries/typescript.scm +4 -0
- docs_drift/index/routes_common.py +16 -0
- docs_drift/index/tasks.py +131 -0
- docs_drift/index/ts_parser.py +20 -0
- docs_drift/limits.py +18 -0
- docs_drift/llm/__init__.py +7 -0
- docs_drift/llm/candidates.py +81 -0
- docs_drift/llm/client.py +185 -0
- docs_drift/llm/pricing.py +54 -0
- docs_drift/llm/privacy.py +63 -0
- docs_drift/llm/prompts/rewrite_v1.md +9 -0
- docs_drift/llm/prompts/verify_v1.md +12 -0
- docs_drift/llm/prompts.py +28 -0
- docs_drift/llm/retrieve.py +60 -0
- docs_drift/llm/types.py +68 -0
- docs_drift/llm/verify.py +179 -0
- docs_drift/lsp.py +199 -0
- docs_drift/match/__init__.py +5 -0
- docs_drift/match/commands.py +201 -0
- docs_drift/match/defaults.py +79 -0
- docs_drift/match/engine.py +319 -0
- docs_drift/match/rename.py +69 -0
- docs_drift/models.py +249 -0
- docs_drift/ownership.py +70 -0
- docs_drift/policy.py +81 -0
- docs_drift/portfolio.py +147 -0
- docs_drift/py.typed +0 -0
- docs_drift/report/__init__.py +38 -0
- docs_drift/report/common.py +31 -0
- docs_drift/report/github.py +22 -0
- docs_drift/report/html.py +106 -0
- docs_drift/report/json.py +17 -0
- docs_drift/report/junit.py +30 -0
- docs_drift/report/markdown.py +58 -0
- docs_drift/report/partials/body.html +19 -0
- docs_drift/report/partials/report.css +12 -0
- docs_drift/report/partials/script.js +95 -0
- docs_drift/report/portfolio.html +14 -0
- docs_drift/report/report.html +8 -0
- docs_drift/report/sarif.py +68 -0
- docs_drift/report/terminal.py +90 -0
- docs_drift/report/vendor/CYTOSCAPE-LICENSE +19 -0
- docs_drift/report/vendor/cytoscape.min.js +32 -0
- docs_drift/report/workflow.html +12 -0
- docs_drift/review.py +113 -0
- docs_drift/safe_files.py +19 -0
- docs_drift/scanning.py +202 -0
- docs_drift/schemas/SARIF-LICENSE.md +7 -0
- docs_drift/schemas/SARIF-NOTICES.txt +3 -0
- docs_drift/schemas/sarif-schema-2.1.0.json +3389 -0
- docs_drift/schemas/scan-result.v1.json +1024 -0
- docs_drift/setup.py +130 -0
- docs_drift/snapshots.py +100 -0
- docs_drift/support.py +31 -0
- docs_drift/watch.py +157 -0
- docs_drift/web/__init__.py +1 -0
- docs_drift/web/__main__.py +23 -0
- docs_drift/web/app.py +289 -0
- docs_drift/web/cleanup.py +23 -0
- docs_drift/web/examples/clean-repo.json +646 -0
- docs_drift/web/examples/fastapi-svc.json +723 -0
- docs_drift/web/examples/files-to-prompt.json +3366 -0
- docs_drift/web/examples/judge-fixture.json +226 -0
- docs_drift/web/examples/manifest.json +89 -0
- docs_drift/web/examples/node-tool.json +823 -0
- docs_drift/web/examples/py-cli.json +1267 -0
- docs_drift/web/examples/rich-cli-reviewed.json +7855 -0
- docs_drift/web/examples/rich-cli.json +8010 -0
- docs_drift/web/examples/verification.json +16 -0
- docs_drift/web/git.py +274 -0
- docs_drift/web/jobs.py +352 -0
- docs_drift/web/settings.py +50 -0
- docs_drift/web/static/demo.css +37 -0
- docs_drift/web/static/demo.js +184 -0
- docs_drift/web/static/evidence/correction-verification.json +28 -0
- docs_drift/web/static/evidence/flow-highlights-2026-10-22.png +0 -0
- docs_drift/web/static/evidence/fresh-scan.json +7855 -0
- docs_drift/web/static/evidence/review.json +44 -0
- docs_drift/web/static/evidence/rich-cli-correction.patch +20 -0
- docs_drift/web/static/evidence/runtime.json +26 -0
- docs_drift/web/static/evidence/saved-scan.json +8010 -0
- docs_drift/web/static/evidence/usage-card-supplied.png +0 -0
- docs_drift/web/templates/base.html +34 -0
- docs_drift/web/templates/case.html +13 -0
- docs_drift/web/templates/landing.html +90 -0
- docs_drift/web/templates/scan.html +2 -0
- docs_drift/web/templates/voice.html +1 -0
- docs_drift/web/validation.py +47 -0
- docs_drift/web/worker.py +152 -0
- docs_drift/workflow_cli.py +96 -0
- docs_drift/workspaces.py +128 -0
- docs_drift-0.1.0.dist-info/METADATA +331 -0
- docs_drift-0.1.0.dist-info/RECORD +131 -0
- docs_drift-0.1.0.dist-info/WHEEL +4 -0
- docs_drift-0.1.0.dist-info/entry_points.txt +2 -0
- docs_drift-0.1.0.dist-info/licenses/LICENSE +21 -0
docs_drift/__init__.py
ADDED
docs_drift/analysis.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Aggregate evidence-backed findings and select findings touched by a Git change."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from docs_drift.config import Config
|
|
9
|
+
from docs_drift.docs.pipeline import _head
|
|
10
|
+
from docs_drift.history import _run
|
|
11
|
+
from docs_drift.models import ScanResult
|
|
12
|
+
from docs_drift.scanning import run_scan
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def insights(result: ScanResult) -> dict[str, Any]:
|
|
16
|
+
drift = [f for f in result.findings if f.is_drift and f.confidence >= result.config_used.min_confidence]
|
|
17
|
+
return {
|
|
18
|
+
"head_sha": result.head_sha,
|
|
19
|
+
"claims": len(result.claims),
|
|
20
|
+
"verdicts": result.summary()["by_verdict"],
|
|
21
|
+
"drift_by_document": dict(sorted(Counter(f.claim.doc_path for f in drift).items())),
|
|
22
|
+
"drift_by_package": dict(sorted(Counter(f.package or "." for f in drift).items())),
|
|
23
|
+
"with_introducing_commit": sum(f.introduced_by is not None for f in drift),
|
|
24
|
+
"warnings": result.warnings,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def changed_paths(repo: Path, base: str, head: str) -> list[str]:
|
|
29
|
+
for ref in (base, head):
|
|
30
|
+
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_./~^{}-]{0,199}", ref) or ".." in ref:
|
|
31
|
+
raise ValueError("Use a single Git revision without options, whitespace or revision ranges.")
|
|
32
|
+
result = _run(repo, ["diff", "--no-ext-diff", "--no-textconv", "--name-only", "-z", base, head, "--"])
|
|
33
|
+
if result is None:
|
|
34
|
+
raise ValueError("Cannot compare these revisions. Fetch the base commit or choose a revision that exists.")
|
|
35
|
+
return sorted(p.replace("\\", "/") for p in result.stdout.split("\0") if p)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def impact(repo: Path, config: Config, base: str, head: str = "HEAD") -> ScanResult:
|
|
39
|
+
"""Report current findings linked to changed docs/source; not a runtime dependency proof."""
|
|
40
|
+
paths = set(changed_paths(repo, base, head))
|
|
41
|
+
resolved = _run(repo, ["rev-parse", "--verify", head])
|
|
42
|
+
if not resolved or resolved.stdout.strip() != _head(repo):
|
|
43
|
+
raise ValueError("Impact scans the current checkout; --head must resolve to its HEAD.")
|
|
44
|
+
result = run_scan(repo, config)
|
|
45
|
+
selected = [
|
|
46
|
+
f
|
|
47
|
+
for f in result.findings
|
|
48
|
+
if f.claim.doc_path in paths
|
|
49
|
+
or (f.matched_symbol and f.matched_symbol.file in paths)
|
|
50
|
+
or any(e.file in paths for e in f.evidence)
|
|
51
|
+
]
|
|
52
|
+
return result.model_copy(update={"findings": selected, "claims": [f.claim for f in selected]})
|
docs_drift/baseline.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Read and write explicit finding baselines without running scanned code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
9
|
+
|
|
10
|
+
from docs_drift.models import DecidedBy, Finding, Verdict
|
|
11
|
+
from docs_drift.safe_files import metadata_text
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaselineEntry(BaseModel):
|
|
15
|
+
model_config = ConfigDict(extra="forbid")
|
|
16
|
+
|
|
17
|
+
finding_id: str = Field(min_length=1)
|
|
18
|
+
verdict: Verdict
|
|
19
|
+
doc_path: str = Field(min_length=1)
|
|
20
|
+
claim: str = Field(min_length=1)
|
|
21
|
+
decided_by: DecidedBy = DecidedBy.rules
|
|
22
|
+
replayed: bool = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FindingBaseline(BaseModel):
|
|
26
|
+
model_config = ConfigDict(extra="forbid")
|
|
27
|
+
|
|
28
|
+
schema_version: Literal[1] = 1
|
|
29
|
+
findings: list[BaselineEntry] = Field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def finding_id(finding: Finding) -> str:
|
|
33
|
+
"""Claim IDs are stable across line shifts and suitable for baseline updates."""
|
|
34
|
+
return finding.claim.claim_id
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_baseline(path: Path) -> FindingBaseline:
|
|
38
|
+
try:
|
|
39
|
+
return FindingBaseline.model_validate_json(metadata_text(path))
|
|
40
|
+
except ValidationError:
|
|
41
|
+
# Pydantic's default diagnostics can echo claim text from the input file.
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"Invalid baseline JSON or schema at {path}. Regenerate it from a reviewed scan with baseline update."
|
|
44
|
+
) from None
|
|
45
|
+
except OSError as exc:
|
|
46
|
+
raise ValueError(f"Cannot read baseline {path}: {exc}. Check the path and permissions.") from exc
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def save_baseline(path: Path, findings: list[Finding]) -> None:
|
|
50
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
entries = [
|
|
52
|
+
BaselineEntry(
|
|
53
|
+
finding_id=finding_id(item),
|
|
54
|
+
verdict=item.verdict,
|
|
55
|
+
doc_path=item.claim.doc_path,
|
|
56
|
+
claim=item.claim.value,
|
|
57
|
+
decided_by=item.decided_by,
|
|
58
|
+
replayed=item.replayed,
|
|
59
|
+
)
|
|
60
|
+
for item in findings
|
|
61
|
+
if item.is_drift
|
|
62
|
+
]
|
|
63
|
+
entries.sort(key=lambda item: (item.doc_path, item.claim, item.finding_id))
|
|
64
|
+
path.write_text(FindingBaseline(findings=entries).model_dump_json(indent=2) + "\n", encoding="utf-8")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def new_findings(findings: list[Finding], baseline: FindingBaseline) -> list[Finding]:
|
|
68
|
+
# A changed verdict must be reviewed again even when the claim stayed put.
|
|
69
|
+
known = {(entry.finding_id, entry.verdict) for entry in baseline.findings}
|
|
70
|
+
return [item for item in findings if item.is_drift and (finding_id(item), item.verdict) not in known]
|