Tak. To, co opisujesz, to drugi tryb pracy obok „szukania duplikatów intencji”:
1. detect intent duplicates
Czy dwie części kodu mają tę samą intencję?
2. validate intent conformance
Czy dana linia / funkcja / plik / projekt faktycznie odpowiada zadeklarowanej intencji?
Czyli @intent nie służy tylko do wykrywania duplikatów. Może działać też jak mini-kontrakt projektowy.
To pasuje do wcześniejszego założenia, żeby nowy moduł działał na istniejących CodeBlock, bez przebudowy skanera, i żeby mieć osobny pakiet src/redup/core/intent/ z parserem, sygnaturą, scoringiem i detektorem.
Proponuję dodać do DSL dwa pola:
req: wymagane pod-intencje
no: rzeczy zakazane
Pełny format:
@intent <action>:<object> !pN @domain scope:<scope> in:<inputs> out:<outputs> fx:<effects> no:<forbidden> req:<required_intents> #tags
Przykład:
# @intent analyze:code_duplication !p1 @project scope:project in:source_tree out:DuplicationMap fx:read no:network req:scan.project_files,extract.code_blocks,detect.duplicates,render.report #dedupZnaczenie:
Projekt ma analizować duplikację kodu.
Wejście: source_tree.
Wyjście: DuplicationMap.
Efekt uboczny: odczyt plików.
Zakazane: network.
Wymagane pod-intencje:
- scan.project_files
- extract.code_blocks
- detect.duplicates
- render.report
# @intent normalize:path !p3 @utils scope:line in:raw_path out:absolute_path fx:none no:network
absolute_path = Path(raw_path).expanduser().resolve()action = normalize
object = path
scope = line
input raw_path występuje w linii
output absolute_path jest przypisany
fx:none — brak oczywistych efektów ubocznych
no:network — brak requests/http/socket
{
"intent": "normalize:path",
"scope": "line",
"status": "pass",
"score": 0.94,
"matched": {
"input": ["raw_path"],
"output": ["absolute_path"],
"forbidden_absent": ["network"]
},
"missing": [],
"violations": []
}# @intent normalize:path !p3 @utils scope:line in:raw_path out:absolute_path fx:none no:network
absolute_path = requests.get(raw_path).textWtedy:
{
"intent": "normalize:path",
"scope": "line",
"status": "violation",
"score": 0.61,
"violations": [
"forbidden effect detected: network"
]
}# @intent validate:user_permission !p1 @security scope:function in:user,resource out:allowed fx:none no:write #auth
def can_update_resource(user, resource):
return user.is_admin or resource.owner_id == user.id1. Czy funkcja ma wejścia user/resource albo podobne nazwy.
2. Czy zwraca wartość logiczną / decyzję.
3. Czy nie zapisuje do pliku/bazy.
4. Czy nie wykonuje efektów ubocznych zakazanych przez no:write.
5. Czy domena security i tag auth są zgodne z intencją.
{
"intent": "validate:user_permission",
"scope": "function",
"status": "pass",
"score": 0.91,
"evidence": {
"inputs_found": ["user", "resource"],
"returns_value": true,
"forbidden_effects_absent": ["write"],
"domain": "security"
}
}# @intent validate:user_permission !p1 @security scope:function in:user,resource out:allowed fx:none no:write #auth
def can_update_resource(user, resource):
audit_log.write_text("checked")
return user.is_adminWynik:
{
"intent": "validate:user_permission",
"scope": "function",
"status": "violation",
"score": 0.78,
"violations": [
"declared no:write but write effect was detected"
]
}Na początku pliku src/redup/cli_app/scan_commands.py:
# @intent expose:scan_cli !p2 @cli scope:file in:path,options out:DuplicationMap fx:read no:network req:build.scan_config,analyze.duplication,render.output #cli# @intent build:scan_config !p1 @scanner scope:function in:path,options out:ScanConfig fx:none
def build_scan_config(...):
...
# @intent analyze:duplication !p1 @analysis scope:function in:ScanConfig out:DuplicationMap fx:read
def run_analysis(...):
...
# @intent render:output !p3 @reporting scope:function in:DuplicationMap out:report fx:write
def write_results(...):
...1. Czy plik ma intencję file-level.
2. Czy w tym pliku lub zależnym module istnieją wymagane req.
3. Czy efekty uboczne pliku mieszczą się w deklaracji.
4. Czy nie ma efektów zakazanych.
5. Czy plik nie deklaruje jednej rzeczy, a faktycznie robi coś zupełnie innego.
{
"intent": "expose:scan_cli",
"scope": "file",
"status": "partial",
"score": 0.82,
"requirements": {
"satisfied": [
"build.scan_config",
"analyze.duplication"
],
"missing": [
"render.output"
]
},
"violations": []
}Czyli plik częściowo odpowiada intencji, ale brakuje jednej wymaganej pod-intencji.
Najlepiej dodać plik:
.redup.intent
albo sekcję w SUMD.md.
@intent analyze:code_duplication !p1 @project scope:project in:source_tree out:DuplicationMap,RefactorSuggestion fx:read no:network req:scan.project_files,extract.code_blocks,detect.duplicates,group.duplicates,render.report #dedup #analysis
@intent expose:cli !p2 @interface scope:project in:argv out:terminal_report fx:read,write req:parse.cli_options,build.scan_config,run.analysis #cli
@intent expose:mcp_tools !p2 @mcp scope:project in:json_rpc_request out:json_rpc_response fx:read req:validate.tool_schema,handle.analyze_project,format.analysis_result #mcp
@intent plan:refactoring !p2 @planning scope:project in:DuplicationMap out:RefactorSuggestion fx:none req:rank.duplicates,assess.risk,suggest.refactor_action #planning
Dla każdej wymaganej intencji projektowej sprawdza, czy w kodzie istnieją pasujące intencje niższego poziomu.
Przykład kodu:
# @intent scan:project_files !p1 @scanner scope:function in:ScanConfig out:file_list fx:read
def _collect_files(...):
...
# @intent extract:code_blocks !p1 @scanner scope:function in:source_file out:CodeBlock fx:read
def _extract_blocks_for_file(...):
...
# @intent detect:duplicates !p1 @analysis scope:function in:CodeBlock out:DuplicateGroup fx:none
def find_duplicates_phase_optimized(...):
...
# @intent render:report !p3 @reporting scope:function in:DuplicationMap out:json,markdown fx:write
def write_results(...):
...{
"project": "redup",
"status": "partial",
"coverage": {
"required": 4,
"passed": 2,
"partial": 1,
"failed": 1
},
"intents": [
{
"intent": "analyze:code_duplication",
"status": "pass",
"score": 0.93,
"satisfied_requirements": [
"scan.project_files",
"extract.code_blocks",
"detect.duplicates",
"group.duplicates",
"render.report"
],
"missing_requirements": []
},
{
"intent": "expose:cli",
"status": "pass",
"score": 0.89,
"satisfied_requirements": [
"parse.cli_options",
"build.scan_config",
"run.analysis"
],
"missing_requirements": []
},
{
"intent": "expose:mcp_tools",
"status": "partial",
"score": 0.76,
"satisfied_requirements": [
"handle.analyze_project",
"format.analysis_result"
],
"missing_requirements": [
"validate.tool_schema"
]
},
{
"intent": "plan:refactoring",
"status": "fail",
"score": 0.41,
"satisfied_requirements": [
"assess.risk"
],
"missing_requirements": [
"rank.duplicates",
"suggest.refactor_action"
]
}
],
"violations": [
{
"intent": "analyze:code_duplication",
"violation": "declared no:network but network usage was detected in semantic/LLM path",
"severity": "warning"
}
]
}To odpowiada dokładnie na pytanie: którym intencjom projekt odpowiada, którym odpowiada częściowo, a którym nie odpowiada.
Proponuję pięć statusów:
pass
Kod/projekt odpowiada intencji.
partial
Intencja jest częściowo pokryta, ale brakuje części req albo score jest za niski.
fail
Brak wystarczającego dopasowania.
violation
Intencja pasuje, ale kod łamie zakaz, np. no:network albo no:write.
unknown
Brakuje danych do walidacji.
Progi:
score >= 0.84 pass
0.65–0.83 partial
< 0.65 fail
violation zawsze violation, nawet przy wysokim score
Poniżej masz szkic modułu, który można dodać jako:
src/redup/core/intent/validation.py
Zakładam, że masz już wcześniejszy moduł ridl.py z funkcjami:
extract_intent_signatures_from_text
score_intent_similarity
feature_values
normalize_labelfrom __future__ import annotations
import re
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from redup.core.intent.ridl import (
IntentSignature,
extract_intent_signatures_from_text,
feature_values,
normalize_label,
score_intent_similarity,
)
class IntentValidationStatus(str, Enum):
PASS = "pass"
PARTIAL = "partial"
FAIL = "fail"
VIOLATION = "violation"
UNKNOWN = "unknown"
@dataclass(frozen=True)
class IntentValidationIssue:
kind: str
message: str
severity: str = "error"
@dataclass
class IntentValidationResult:
required_intent: str
status: IntentValidationStatus
score: float = 0.0
matched_intent_id: str | None = None
matched_file: str | None = None
matched_lines: tuple[int, int] | None = None
satisfied_requirements: list[str] = field(default_factory=list)
missing_requirements: list[str] = field(default_factory=list)
violations: list[IntentValidationIssue] = field(default_factory=list)
evidence: dict[str, object] = field(default_factory=dict)
@dataclass
class ProjectIntentValidationReport:
project_path: str
status: IntentValidationStatus
results: list[IntentValidationResult]
@property
def passed(self) -> list[IntentValidationResult]:
return [r for r in self.results if r.status == IntentValidationStatus.PASS]
@property
def partial(self) -> list[IntentValidationResult]:
return [r for r in self.results if r.status == IntentValidationStatus.PARTIAL]
@property
def failed(self) -> list[IntentValidationResult]:
return [r for r in self.results if r.status == IntentValidationStatus.FAIL]
@property
def violations(self) -> list[IntentValidationResult]:
return [r for r in self.results if r.status == IntentValidationStatus.VIOLATION]
def intent_key(signature: IntentSignature) -> str:
return f"{signature.action}.{signature.object}"
def parse_required_keys_from_raw(raw: str) -> set[str]:
"""
Czyta req:scan.project_files,extract.code_blocks z surowego tagu.
"""
result: set[str] = set()
for match in re.finditer(r"\breq:([^\s]+)", raw):
values = match.group(1).split(",")
for value in values:
normalized = normalize_requirement_key(value)
if normalized:
result.add(normalized)
return result
def normalize_requirement_key(value: str) -> str:
"""
Normalizuje:
scan.project_files -> scan.project_file
scan:project_files -> scan.project_file
scan_project_files -> scan_project_file
"""
value = value.strip()
if not value:
return ""
value = value.replace(":", ".")
parts = value.split(".")
if len(parts) == 1:
return normalize_label(parts[0])
action = normalize_label(parts[0])
obj = normalize_label("_".join(parts[1:]))
return f"{action}.{obj}"
def detect_observed_effects(source: str) -> set[str]:
"""
Prosta, deterministyczna heurystyka efektów ubocznych.
Nie udaje pełnej analizy semantycznej, ale daje dobry MVP.
"""
effects: set[str] = set()
network_patterns = [
r"\brequests\.",
r"\bhttpx\.",
r"\burllib\.",
r"\bsocket\.",
r"\bfetch\(",
]
write_patterns = [
r"\.write\(",
r"\.write_text\(",
r"\bopen\([^)]*['\"]w",
r"\bINSERT\b",
r"\bUPDATE\b",
r"\bDELETE\b",
]
read_patterns = [
r"\.read\(",
r"\.read_text\(",
r"\bopen\(",
r"\bSELECT\b",
]
if any(re.search(pattern, source) for pattern in network_patterns):
effects.add("network")
if any(re.search(pattern, source, re.IGNORECASE) for pattern in write_patterns):
effects.add("write")
if any(re.search(pattern, source, re.IGNORECASE) for pattern in read_patterns):
effects.add("read")
if re.search(r"\bprint\(", source) or re.search(r"\blogger\.", source):
effects.add("log")
return effects
def validate_forbidden_effects(
required: IntentSignature,
observed_source: str,
) -> list[IntentValidationIssue]:
forbidden = feature_values(required, "no")
observed_effects = detect_observed_effects(observed_source)
issues: list[IntentValidationIssue] = []
for item in forbidden:
if item in observed_effects:
issues.append(
IntentValidationIssue(
kind="forbidden_effect",
message=f"Declared no:{item}, but effect '{item}' was detected.",
severity="error",
)
)
return issues
def find_best_matching_intent(
required: IntentSignature,
observed: list[IntentSignature],
) -> tuple[IntentSignature | None, float, dict[str, object]]:
best: IntentSignature | None = None
best_score = 0.0
best_reason: dict[str, object] = {}
for candidate in observed:
score, reason = score_intent_similarity(required, candidate)
if score > best_score:
best = candidate
best_score = score
best_reason = reason
return best, best_score, best_reason
def build_intent_index(signatures: list[IntentSignature]) -> dict[str, list[IntentSignature]]:
index: dict[str, list[IntentSignature]] = {}
for signature in signatures:
index.setdefault(intent_key(signature), []).append(signature)
return index
def validate_required_subintents(
required: IntentSignature,
observed_index: dict[str, list[IntentSignature]],
) -> tuple[list[str], list[str]]:
required_keys = parse_required_keys_from_raw(required.raw)
satisfied: list[str] = []
missing: list[str] = []
for key in sorted(required_keys):
if key in observed_index and observed_index[key]:
satisfied.append(key)
else:
missing.append(key)
return satisfied, missing
def classify_validation_status(
score: float,
missing_requirements: list[str],
violations: list[IntentValidationIssue],
*,
pass_threshold: float = 0.84,
partial_threshold: float = 0.65,
) -> IntentValidationStatus:
if violations:
return IntentValidationStatus.VIOLATION
if score >= pass_threshold and not missing_requirements:
return IntentValidationStatus.PASS
if score >= partial_threshold or missing_requirements:
return IntentValidationStatus.PARTIAL
return IntentValidationStatus.FAIL
def validate_intent_against_project(
required: IntentSignature,
observed: list[IntentSignature],
observed_sources: dict[str, str],
*,
pass_threshold: float = 0.84,
partial_threshold: float = 0.65,
) -> IntentValidationResult:
observed_index = build_intent_index(observed)
best, score, reason = find_best_matching_intent(required, observed)
satisfied, missing = validate_required_subintents(required, observed_index)
violations: list[IntentValidationIssue] = []
if best is not None:
source = observed_sources.get(best.file_path, "")
violations.extend(validate_forbidden_effects(required, source))
status = classify_validation_status(
score,
missing,
violations,
pass_threshold=pass_threshold,
partial_threshold=partial_threshold,
)
return IntentValidationResult(
required_intent=intent_key(required),
status=status,
score=round(score, 4),
matched_intent_id=best.block_id if best else None,
matched_file=best.file_path if best else None,
matched_lines=(best.start_line, best.end_line) if best else None,
satisfied_requirements=satisfied,
missing_requirements=missing,
violations=violations,
evidence=reason,
)
def extract_required_intents_from_manifest(
manifest_text: str,
*,
file_path: str = ".redup.intent",
) -> list[IntentSignature]:
return extract_intent_signatures_from_text(
manifest_text,
file_path=file_path,
default_scope="project",
)
def extract_observed_project_intents(
sources: dict[str, str],
) -> list[IntentSignature]:
signatures: list[IntentSignature] = []
for file_path, source in sources.items():
signatures.extend(
extract_intent_signatures_from_text(
source,
file_path=file_path,
default_scope="block",
)
)
return signatures
def validate_project_intents(
project_path: str,
manifest_text: str,
sources: dict[str, str],
*,
pass_threshold: float = 0.84,
partial_threshold: float = 0.65,
) -> ProjectIntentValidationReport:
required = extract_required_intents_from_manifest(manifest_text)
observed = extract_observed_project_intents(sources)
results = [
validate_intent_against_project(
item,
observed,
sources,
pass_threshold=pass_threshold,
partial_threshold=partial_threshold,
)
for item in required
]
if any(result.status == IntentValidationStatus.VIOLATION for result in results):
overall = IntentValidationStatus.VIOLATION
elif all(result.status == IntentValidationStatus.PASS for result in results):
overall = IntentValidationStatus.PASS
elif any(result.status in {IntentValidationStatus.PARTIAL, IntentValidationStatus.PASS} for result in results):
overall = IntentValidationStatus.PARTIAL
else:
overall = IntentValidationStatus.FAIL
return ProjectIntentValidationReport(
project_path=project_path,
status=overall,
results=results,
)
def load_project_sources(root: Path, extensions: tuple[str, ...] = (".py", ".js", ".ts", ".java", ".cs", ".go", ".rs")) -> dict[str, str]:
sources: dict[str, str] = {}
for path in root.rglob("*"):
if not path.is_file():
continue
if path.suffix not in extensions:
continue
if any(part in {".git", ".venv", "venv", "__pycache__", "node_modules"} for part in path.parts):
continue
rel_path = str(path.relative_to(root))
try:
sources[rel_path] = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
continue
return sourcesfrom pathlib import Path
from redup.core.intent.validation import (
load_project_sources,
validate_project_intents,
)
root = Path(".")
manifest = Path(".redup.intent").read_text(encoding="utf-8")
sources = load_project_sources(root)
report = validate_project_intents(
project_path=str(root),
manifest_text=manifest,
sources=sources,
)
print(report.status)
for result in report.results:
print(result.required_intent, result.status, result.score)
if result.missing_requirements:
print(" missing:", result.missing_requirements)
for violation in result.violations:
print(" violation:", violation.message)Dodałbym nową komendę:
redup intent validate .Opcje:
redup intent validate . --manifest .redup.intent
redup intent validate . --format json
redup intent validate . --fail-on-missing
redup intent validate . --threshold 0.84Przykład:
python -m redup intent validate . --manifest .redup.intent --format jsonOutput:
{
"status": "partial",
"passed": [
"analyze.code_duplication",
"expose.cli"
],
"partial": [
"expose.mcp_tool"
],
"failed": [
"plan.refactoring"
],
"violations": [
{
"intent": "analyze.code_duplication",
"message": "Declared no:network, but effect 'network' was detected."
}
]
}Masz wtedy trzy osobne funkcje:
1. redup scan --intent
Szuka duplikatów intencji.
2. redup intent validate
Sprawdza, czy kod odpowiada deklarowanym intencjom.
3. redup intent coverage
Pokazuje, jaka część projektu ma opisane intencje.
Przykład coverage:
{
"files_total": 115,
"files_with_intent": 37,
"functions_total": 358,
"functions_with_intent": 92,
"coverage_percent": 25.7,
"missing_high_priority": [
"src/redup/core/pipeline/duplicate_finder.py",
"src/redup/mcp/handlers.py"
]
}Walidacja intencji nie powinna udawać pełnego dowodu formalnego. To powinien być praktyczny, statyczny kontrakt jakościowy.
Czyli:
@intent mówi:
co kod powinien robić,
czego nie powinien robić,
jakie pod-intencje powinny istnieć.
validator sprawdza:
czy deklaracje są obecne,
czy są podobne do wymaganych,
czy są spełnione req,
czy nie ma złamanych no:,
czy są braki i niespójności.
Dzięki temu możesz odpowiedzieć automatycznie:
Projekt odpowiada intencjom:
- analyze:code_duplication
- expose:cli
Projekt częściowo odpowiada:
- expose:mcp_tools
Projekt nie odpowiada:
- plan:refactoring
Projekt łamie:
- analyze:code_duplication no:network
I to wszystko bez LLM, czysto algorytmicznie.