utility relationships understanding of INTENT.md vs. SCOPE.md and documentation.

This commit is contained in:
2026-05-02 17:33:58 +02:00
parent 11c5beba58
commit d9df4da12a
7 changed files with 311 additions and 28 deletions

View File

@@ -74,6 +74,7 @@ class CandidateGraphGenerator:
credential_configs = self._facts(facts, "credential_config")
provider_registries = self._facts(facts, "provider_registry")
fallback_policies = self._facts(facts, "fallback_policy")
intent_facts = self._facts(facts, "intent")
ability_primary_class, ability_attributes = self._ability_classification(
repository,
facts,
@@ -103,6 +104,9 @@ class CandidateGraphGenerator:
capabilities.append(
self._interface_capability(interfaces, tests, examples, docs, chunks)
)
capabilities.extend(
self._intent_capabilities(intent_facts, chunks, tests, examples, docs)
)
promotable_llm_providers = self._promotable_llm_facts(llm_providers)
promotable_provider_registries = self._promotable_llm_facts(provider_registries)
promotable_fallback_policies = self._promotable_llm_facts(fallback_policies)
@@ -139,11 +143,11 @@ class CandidateGraphGenerator:
languages=languages,
docs=docs,
),
source_refs=self._source_refs(manifests + frameworks + languages),
primary_class="repository-structure",
attributes=self._structure_attributes(
manifests,
frameworks,
source_refs=self._source_refs(manifests + frameworks + languages),
primary_class="repository-structure",
attributes=self._structure_attributes(
manifests,
frameworks,
languages,
),
evidence=self._evidence(tests, examples, docs),
@@ -284,6 +288,91 @@ class CandidateGraphGenerator:
evidence=self._evidence(tests, examples, docs),
)
def _intent_capabilities(
self,
intent_facts: list[ObservedFact],
chunks: list[ContentChunk],
tests: list[ObservedFact],
examples: list[ObservedFact],
docs: list[ObservedFact],
) -> list[CandidateCapabilityDraft]:
intent_chunks = [
chunk
for chunk in chunks
if chunk.kind == "intent"
and (
chunk.metadata.get("source_role") == "intent_summary"
or chunk.path.lower().endswith("intent.md")
)
]
if not intent_chunks:
return []
source_refs = self._source_refs(intent_facts)
capabilities: list[CandidateCapabilityDraft] = []
seen: set[str] = set()
for item in self._intent_capability_items(intent_chunks):
name = self._intent_capability_name(item)
key = name.lower()
if not name or key in seen:
continue
seen.add(key)
capabilities.append(
CandidateCapabilityDraft(
name=name,
description=(
"Reviewable intended capability extracted from repository "
f"intent: {item}"
),
inputs=[],
outputs=[name],
confidence=self._confidence(
0.45,
[
(0.15, bool(source_refs)),
(0.10, bool(tests)),
(0.05, bool(examples)),
(0.05, bool(docs)),
],
),
source_refs=source_refs,
primary_class="intent-capability",
attributes=[
"intent-derived",
"utility-owned",
"review-required-intent",
],
evidence=self._evidence(tests, examples, docs),
)
)
return capabilities
def _intent_capability_items(self, chunks: list[ContentChunk]) -> list[str]:
items: list[str] = []
in_capability_section = False
for chunk in sorted(chunks, key=lambda item: (item.path, item.start_line)):
for raw_line in chunk.text.splitlines():
line = raw_line.strip()
if not line:
continue
if line.startswith("#"):
heading = line.lstrip("#").strip().lower()
in_capability_section = "capabilit" in heading
continue
if not in_capability_section:
continue
item = re.sub(r"^(?:[-*]|\d+[.)])\s+", "", line).strip()
item = re.sub(r"^(?:capability|intended capability)\s*:\s*", "", item, flags=re.I)
if item and item != line or raw_line.lstrip().startswith(("-", "*")):
items.append(item)
return items
def _intent_capability_name(self, text: str) -> str:
candidate = re.split(r"\s+-\s+|\s*:\s*|[.!?]\s+", text.strip(), maxsplit=1)[0]
candidate = candidate.strip(" .:-")
if not candidate:
return ""
return self._title_from_words(candidate.split()[:8])
def _interface_features(
self,
interfaces: list[ObservedFact],
@@ -437,7 +526,7 @@ class CandidateGraphGenerator:
def _interface_attributes(self, interfaces: list[ObservedFact]) -> list[str]:
feature_types = {self._feature_type(fact) for fact in interfaces}
attributes = ["api" if item == "API" else "cli" if item == "CLI" else "callable" for item in feature_types]
return self._unique(["surface", *attributes])
return self._unique(["surface", *attributes, "utility-owned"])
def _feature_attributes(
self,
@@ -467,6 +556,9 @@ class CandidateGraphGenerator:
"manifest" if manifests else "",
*[fact.name for fact in frameworks],
*[fact.name for fact in languages],
"utility-dependency" if manifests or frameworks else "",
"utility-tooling" if languages and not (manifests or frameworks) else "",
"review-required-structural-context",
]
)