refactor(core): restart Anchor from original drift detection architecture - #40
Merged
Conversation
Anchor AI Governance Check FailedDetailed report not found. The check may have crashed. Run |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR pivots Anchor back toward the original “drift detection” architecture, simplifying the core implementation (AST-based parsing, history anchoring, usage extraction, deterministic verdicts) while updating docs/tests and project packaging metadata to reflect an in-progress rebuild.
Changes:
- Replaces parts of the previous v5 governance-oriented engine with a smaller drift-auditing core (parser/history/contexts/verdicts) and an argparse-based CLI entrypoint.
- Adds extensive Django manual-audit documentation and calibration artifacts (findings, invariants/contract, philosophy, fossils JSON).
- Updates packaging/license/docs to reflect the rebuild direction and a slimmer distribution story.
Reviewed changes
Copilot reviewed 25 out of 30 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
USAGE.md |
Replaces CLI reference with a usage guide and examples for audit/list + agent output. |
README.md |
Marks the repo as an active rebuild and points to archived v5 tag. |
setup.py |
Resets package metadata/version and changes console entrypoint to anchor.cli:main. |
requirements.txt |
Trims dependency list (but currently diverges from runtime imports). |
LICENSE |
Switches from Apache-2.0 text to MIT. |
manifest.in |
Adds a lowercase manifest file (note: setuptools uses MANIFEST.in). |
.gitignore |
Simplifies ignores and adds brain.db / reports/ ignores. |
anchor/__main__.py |
Updates module entry to call main(). |
anchor/__init__.py |
Removes prior package metadata content (now empty). |
anchor/core/parser.py |
Moves to Python ast visitor for symbol discovery and repo walking. |
anchor/core/history.py |
Simplifies git-history scanning and docstring extraction using ast. |
anchor/core/contexts.py |
Simplifies usage extraction to Python-only AST scanning. |
anchor/core/models.py |
Removes vNext metadata fields and keeps a smaller data model for audits. |
anchor/core/verdicts.py |
Replaces the prior large verdict engine with a simpler role/vote rule engine and agent-facing remediation text. |
tests/test_simple_demo.py |
Adds a demo test module (currently references missing APIs). |
tests/test_djnago_validation.py |
Adds Django validation tests against expected manual-audit verdicts (currently environment-dependent). |
examples/django_fossils.json |
Stores extracted “intent fossils” for Django calibration symbols. |
docs/test_results_analysis.md |
Documents the current limitation: local-repo-only usage scanning fails for framework exports. |
docs/philosophy.md |
Adds the project philosophy and deterministic-auditing principles. |
docs/internal/invariants.md |
Adds the “detection contract” thresholds/criteria for verdicts. |
docs/django_audit_findings.md |
Adds the consolidated Django audit report and rationale. |
docs/audits/authenticate.md |
Adds a manual audit write-up for authenticate(). |
docs/audits/login.md |
Adds a manual audit write-up for login(). |
docs/audits/form.md |
Adds a manual audit write-up for Form. |
docs/audits/modelform.md |
Adds a manual audit write-up (note: content appears to describe Form). |
docs/audits/manager.md |
Adds a manual audit write-up for Manager. |
docs/audits/user.md |
Adds a manual audit write-up for User. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+75
to
+84
| @pytest.fixture(scope="session") | ||
| def django_repo(): | ||
| """Fixture providing path to Django repository.""" | ||
| # Use the Django repo cloned in D:/ | ||
| django_path = Path("D:/django") | ||
|
|
||
| if not django_path.exists(): | ||
| pytest.skip("Django repository not found at D:/django") | ||
|
|
||
| return str(django_path) |
Comment on lines
+3
to
+10
| from typing import Iterator, Optional | ||
| from anchor.core.models import CodeSymbol | ||
|
|
||
| # 1. Export Query and QueryCursor | ||
| try: | ||
| from tree_sitter import Query, QueryCursor | ||
| except ImportError: | ||
| print("❌ ERROR: Could not import Query/QueryCursor from tree_sitter.") | ||
| raise | ||
| class SymbolVisitor(ast.NodeVisitor): | ||
| def __init__(self, file_path: str): | ||
| self.file_path = file_path | ||
| self.symbols: list[CodeSymbol] = [] | ||
| self.current_class: Optional[str] = None |
Comment on lines
+7
to
+10
| import pytest | ||
| from pathlib import Path | ||
| from anchor.repo import RepositoryAnalyzer | ||
|
|
Comment on lines
+1
to
+3
| include README.md | ||
| include USAGE.md | ||
| include LICENSE No newline at end of file |
Comment on lines
29
to
33
| packages=find_packages(), | ||
| include_package_data=True, | ||
| package_data={ | ||
| "anchor": [ | ||
| "core/resources/*.example", | ||
| "core/resources/*.png", | ||
| "governance/**/*.anchor", | ||
| "governance/examples/*", | ||
| "governance/mitigation.anchor", | ||
| "governance/constitution.anchor", | ||
| ], | ||
| }, | ||
| install_requires=[ | ||
| "click", | ||
| "pyyaml", | ||
| "tree-sitter>=0.22.0", | ||
| "tree-sitter-python", | ||
| "tree-sitter-typescript", | ||
| "tree-sitter-go", | ||
| "tree-sitter-java", | ||
| "tree-sitter-rust", | ||
| "pydantic-settings>=2.0.0", | ||
| "wrapt", | ||
| "requests", | ||
| "GitPython", | ||
| "pyahocorasick", | ||
| "cryptography>=41.0.0", | ||
| "GitPython>=3.1.0", | ||
| ], |
Comment on lines
33
to
+36
| try: | ||
| blob = commit.tree / git_path | ||
| file_content = blob.data_stream.read().decode('utf-8') | ||
|
|
||
Comment on lines
8
to
18
| total_usages = len(contexts) | ||
| symbol_layer = classify_architectural_layer(anchor.original_file_path or "") | ||
| file_importance = get_file_importance_multiplier(anchor.original_file_path or "") | ||
| confidence_level, requires_review = _confidence_level_from_score(anchor.confidence_score) | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # Governance Drift — checked first, highest priority | ||
| # ----------------------------------------------------------------------- | ||
| if anchor.original_file_path: | ||
| has_gov_drift, detected_caps, missing_ctrl, cap_class = check_governance_drift( | ||
| anchor.original_file_path, repo_path | ||
| ) | ||
| if has_gov_drift: | ||
| verdict = VerdictType.GOVERNANCE_DRIFT | ||
| rationale = ( | ||
| f"High-risk capabilities detected ({', '.join(detected_caps)}) " | ||
| f"in {anchor.original_file_path!r} with missing governance controls: " | ||
| f"{', '.join(missing_ctrl)}. Capability class: {cap_class}." | ||
| ) | ||
| remediation = _build_remediation( | ||
| verdict, symbol_name, anchor, [], | ||
| missing_controls=missing_ctrl, | ||
| detected_capabilities=detected_caps, | ||
| capability_class=cap_class, | ||
| ) | ||
| priority = _compute_priority_score(verdict, symbol_layer, | ||
| anchor.original_file_path) | ||
| return AuditResult( | ||
| symbol=symbol_name, anchor=anchor, observed_roles=[], | ||
| verdict=verdict, rationale=rationale, | ||
| evidence=[f"Capability: {c} ({cap_class})" for c in detected_caps], | ||
| remediation=remediation, | ||
| priority_score=priority, | ||
| confidence_level=confidence_level, | ||
| requires_human_review=requires_review, | ||
| detected_capabilities=detected_caps, | ||
| missing_controls=missing_ctrl, | ||
| ) | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # Guard: not enough usage data | ||
| # ----------------------------------------------------------------------- | ||
|
|
||
| if total_usages == 0: | ||
| priority = _compute_priority_score(VerdictType.CONFIDENCE_TOO_LOW, | ||
| symbol_layer, anchor.original_file_path or "") | ||
| return AuditResult( | ||
| symbol=symbol_name, anchor=anchor, observed_roles=[], | ||
| verdict=VerdictType.CONFIDENCE_TOO_LOW, | ||
| rationale="No call sites found in the local repository. " | ||
| "Cannot issue a verdict without usage context.", | ||
| evidence=[], remediation=None, | ||
| priority_score=priority, | ||
| confidence_level=confidence_level, | ||
| requires_human_review=True, | ||
| ) | ||
|
|
||
| if anchor.intent_description in ("", "No docstring found in early history."): | ||
| priority = _compute_priority_score(VerdictType.CONFIDENCE_TOO_LOW, | ||
| symbol_layer, anchor.original_file_path or "") | ||
| return AuditResult( | ||
| symbol=symbol_name, anchor=anchor, observed_roles=[], | ||
| symbol=symbol_name, | ||
| anchor=anchor, | ||
| observed_roles=[], | ||
| verdict=VerdictType.CONFIDENCE_TOO_LOW, | ||
| rationale="Symbol has no documented intent in early git history. " | ||
| "Cannot determine whether current usage is aligned.", | ||
| evidence=[], remediation=None, | ||
| priority_score=priority, | ||
| confidence_level=confidence_level, | ||
| requires_human_review=True, | ||
| rationale="No usages found in codebase.", | ||
| evidence=[] | ||
| ) |
Comment on lines
+35
to
+50
| module_counts: Dict[str, int] = {} | ||
| for ctx in contexts: | ||
| parts = ctx.file_path.replace("\\", "/").split("/") | ||
| if len(parts) > 2: | ||
| # Heuristic: sdks/python -> sdks.python | ||
| domain = f"{parts[0]}.{parts[1]}" | ||
| else: | ||
| domain = "root" | ||
| module_counts[domain] = module_counts.get(domain, 0) + 1 | ||
|
|
||
| for domain, count in module_counts.items(): | ||
| ratio = count / total_usages | ||
| # Heuristic: Compatible if it lives in the same root module | ||
| is_compatible = True # Simplified for generic case | ||
| roles.append(SemanticRole(f"Caller: {domain}", "Module-based usage", count, ratio, is_compatible)) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.