Navegador

navegador / navegador / ingestion / knowledge.py
Blame History Raw 271 lines
1
"""
2
KnowledgeIngester — manual curation of business concepts, rules, decisions,
3
people, and domains into the navegador graph.
4
5
These are the things that don't live in code but belong in the knowledge graph:
6
business rules, architectural decisions, domain groupings, ownership, wiki refs.
7
"""
8
9
import logging
10
from typing import Any
11
12
from navegador.graph.schema import EdgeType, NodeLabel
13
from navegador.graph.store import GraphStore
14
15
logger = logging.getLogger(__name__)
16
17
18
class KnowledgeIngester:
19
"""
20
Writes business knowledge nodes and relationships into the graph.
21
22
Usage:
23
store = GraphStore.sqlite(".navegador/graph.db")
24
k = KnowledgeIngester(store)
25
26
k.add_domain("auth", description="Authentication and authorisation")
27
k.add_concept("JWT", description="Stateless token auth", domain="auth")
28
k.add_rule("tokens must expire", domain="auth", severity="critical",
29
rationale="Security requirement per OWASP")
30
k.annotate_code("validate_token", "Function", concept="JWT")
31
k.wiki_page("JWT Auth", url="...", content="...")
32
"""
33
34
def __init__(self, store: GraphStore) -> None:
35
self.store = store
36
37
# ── Domains ───────────────────────────────────────────────────────────────
38
39
def add_domain(self, name: str, description: str = "") -> None:
40
self.store.create_node(
41
NodeLabel.Domain,
42
{
43
"name": name,
44
"description": description,
45
},
46
)
47
logger.info("Domain: %s", name)
48
49
# ── Concepts ──────────────────────────────────────────────────────────────
50
51
def add_concept(
52
self,
53
name: str,
54
description: str = "",
55
domain: str = "",
56
status: str = "",
57
rules: str = "",
58
examples: str = "",
59
wiki_refs: str = "",
60
) -> None:
61
self.store.create_node(
62
NodeLabel.Concept,
63
{
64
"name": name,
65
"description": description,
66
"domain": domain,
67
"status": status,
68
"rules": rules,
69
"examples": examples,
70
"wiki_refs": wiki_refs,
71
},
72
)
73
if domain:
74
self._link_to_domain(name, NodeLabel.Concept, domain)
75
logger.info("Concept: %s", name)
76
77
def relate_concepts(self, a: str, b: str) -> None:
78
"""Mark two concepts as related (bidirectional intent)."""
79
self.store.create_edge(
80
NodeLabel.Concept,
81
{"name": a},
82
EdgeType.RELATED_TO,
83
NodeLabel.Concept,
84
{"name": b},
85
)
86
87
# ── Rules ─────────────────────────────────────────────────────────────────
88
89
def add_rule(
90
self,
91
name: str,
92
description: str = "",
93
domain: str = "",
94
severity: str = "info",
95
rationale: str = "",
96
examples: str = "",
97
) -> None:
98
self.store.create_node(
99
NodeLabel.Rule,
100
{
101
"name": name,
102
"description": description,
103
"domain": domain,
104
"severity": severity,
105
"rationale": rationale,
106
"examples": examples,
107
},
108
)
109
if domain:
110
self._link_to_domain(name, NodeLabel.Rule, domain)
111
logger.info("Rule: %s", name)
112
113
def rule_governs(self, rule_name: str, target_name: str, target_label: NodeLabel) -> None:
114
self.store.create_edge(
115
NodeLabel.Rule,
116
{"name": rule_name},
117
EdgeType.GOVERNS,
118
target_label,
119
{"name": target_name},
120
)
121
122
# ── Decisions ─────────────────────────────────────────────────────────────
123
124
def add_decision(
125
self,
126
name: str,
127
description: str = "",
128
domain: str = "",
129
status: str = "accepted",
130
rationale: str = "",
131
alternatives: str = "",
132
date: str = "",
133
) -> None:
134
self.store.create_node(
135
NodeLabel.Decision,
136
{
137
"name": name,
138
"description": description,
139
"domain": domain,
140
"status": status,
141
"rationale": rationale,
142
"alternatives": alternatives,
143
"date": date,
144
},
145
)
146
if domain:
147
self._link_to_domain(name, NodeLabel.Decision, domain)
148
logger.info("Decision: %s", name)
149
150
# ── People ────────────────────────────────────────────────────────────────
151
152
def add_person(
153
self,
154
name: str,
155
email: str = "",
156
role: str = "",
157
team: str = "",
158
) -> None:
159
self.store.create_node(
160
NodeLabel.Person,
161
{
162
"name": name,
163
"email": email,
164
"role": role,
165
"team": team,
166
},
167
)
168
logger.info("Person: %s", name)
169
170
def assign(self, target_name: str, target_label: NodeLabel, person_name: str) -> None:
171
"""Assign a person as owner of any node."""
172
self.store.create_edge(
173
target_label,
174
{"name": target_name},
175
EdgeType.ASSIGNED_TO,
176
NodeLabel.Person,
177
{"name": person_name},
178
)
179
180
# ── Wiki pages ────────────────────────────────────────────────────────────
181
182
def wiki_page(
183
self,
184
name: str,
185
url: str = "",
186
source: str = "github",
187
content: str = "",
188
updated_at: str = "",
189
) -> None:
190
self.store.create_node(
191
NodeLabel.WikiPage,
192
{
193
"name": name,
194
"url": url,
195
"source": source,
196
"content": content,
197
"updated_at": updated_at,
198
},
199
)
200
logger.info("WikiPage: %s", name)
201
202
def wiki_documents(
203
self,
204
wiki_page_name: str,
205
target_name: str,
206
target_props: dict[str, Any],
207
target_label: NodeLabel,
208
) -> None:
209
self.store.create_edge(
210
NodeLabel.WikiPage,
211
{"name": wiki_page_name},
212
EdgeType.DOCUMENTS,
213
target_label,
214
target_props,
215
)
216
217
# ── Code ↔ Knowledge bridges ──────────────────────────────────────────────
218
219
def annotate_code(
220
self,
221
code_name: str,
222
code_label: str,
223
concept: str | None = None,
224
rule: str | None = None,
225
) -> None:
226
"""
227
Link a code node to a concept or rule via ANNOTATES.
228
code_label should be a string matching a NodeLabel value.
229
"""
230
label = NodeLabel(code_label)
231
if concept:
232
self.store.create_edge(
233
NodeLabel.Concept,
234
{"name": concept},
235
EdgeType.ANNOTATES,
236
label,
237
{"name": code_name},
238
)
239
if rule:
240
self.store.create_edge(
241
NodeLabel.Rule,
242
{"name": rule},
243
EdgeType.ANNOTATES,
244
label,
245
{"name": code_name},
246
)
247
248
def code_implements(self, code_name: str, code_label: str, concept_name: str) -> None:
249
"""Mark a function/class as implementing a concept."""
250
label = NodeLabel(code_label)
251
self.store.create_edge(
252
label,
253
{"name": code_name},
254
EdgeType.IMPLEMENTS,
255
NodeLabel.Concept,
256
{"name": concept_name},
257
)
258
259
# ── Helpers ───────────────────────────────────────────────────────────────
260
261
def _link_to_domain(self, name: str, label: NodeLabel, domain: str) -> None:
262
# Ensure domain node exists
263
self.store.create_node(NodeLabel.Domain, {"name": domain, "description": ""})
264
self.store.create_edge(
265
label,
266
{"name": name},
267
EdgeType.BELONGS_TO,
268
NodeLabel.Domain,
269
{"name": domain},
270
)
271

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button