devtime-ei 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.
- devtime/__init__.py +9 -0
- devtime/ai/__init__.py +0 -0
- devtime/ai/local.py +11 -0
- devtime/ai/prompts.py +24 -0
- devtime/ai/providers.py +41 -0
- devtime/assets/devtimeignore.starter +23 -0
- devtime/cli.py +374 -0
- devtime/config.py +67 -0
- devtime/db/__init__.py +0 -0
- devtime/db/connection.py +16 -0
- devtime/db/migrations.py +114 -0
- devtime/db/repository.py +351 -0
- devtime/db/schema.sql +145 -0
- devtime/fixtures/__init__.py +0 -0
- devtime/fixtures/assertions.py +51 -0
- devtime/fixtures/loader.py +52 -0
- devtime/fixtures/runner.py +73 -0
- devtime/intelligence/__init__.py +0 -0
- devtime/intelligence/claims.py +235 -0
- devtime/intelligence/concepts.py +483 -0
- devtime/intelligence/context_pack.py +276 -0
- devtime/intelligence/evidence.py +127 -0
- devtime/intelligence/lineage.py +21 -0
- devtime/intelligence/risk.py +267 -0
- devtime/intelligence/scoring.py +99 -0
- devtime/mcp/__init__.py +0 -0
- devtime/mcp/schemas.py +39 -0
- devtime/mcp/server.py +35 -0
- devtime/mcp/tools.py +90 -0
- devtime/output/__init__.py +0 -0
- devtime/output/json_export.py +50 -0
- devtime/output/markdown.py +50 -0
- devtime/output/terminal.py +208 -0
- devtime/paths.py +40 -0
- devtime/privacy.py +96 -0
- devtime/scanner/__init__.py +0 -0
- devtime/scanner/extractors/__init__.py +0 -0
- devtime/scanner/extractors/base.py +83 -0
- devtime/scanner/extractors/config_files.py +41 -0
- devtime/scanner/extractors/docs.py +35 -0
- devtime/scanner/extractors/nextjs.py +82 -0
- devtime/scanner/extractors/python.py +81 -0
- devtime/scanner/extractors/tests.py +61 -0
- devtime/scanner/extractors/typescript.py +99 -0
- devtime/scanner/file_walker.py +96 -0
- devtime/scanner/ignore.py +96 -0
- devtime/scanner/language.py +36 -0
- devtime/scanner/signals.py +252 -0
- devtime_ei-0.1.0.dist-info/METADATA +289 -0
- devtime_ei-0.1.0.dist-info/RECORD +54 -0
- devtime_ei-0.1.0.dist-info/WHEEL +5 -0
- devtime_ei-0.1.0.dist-info/entry_points.txt +2 -0
- devtime_ei-0.1.0.dist-info/licenses/LICENSE +201 -0
- devtime_ei-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Claim generation and governance (Builder Edition, Chapter 11).
|
|
2
|
+
|
|
3
|
+
Claims are structured beliefs, not prose. They are generated from evidence first,
|
|
4
|
+
then rendered. This prevents narration from inventing truth.
|
|
5
|
+
|
|
6
|
+
Core laws enforced here:
|
|
7
|
+
- No claim without evidence.
|
|
8
|
+
- No claim stronger than its evidence.
|
|
9
|
+
- Usage is not decision.
|
|
10
|
+
- Uncertainty is output.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
|
|
17
|
+
from devtime.intelligence.concepts import ConceptCandidate
|
|
18
|
+
from devtime.intelligence.evidence import EvidenceItem
|
|
19
|
+
|
|
20
|
+
CLAIM_TYPES = (
|
|
21
|
+
"concept", "usage", "behavior", "decision", "test", "risk",
|
|
22
|
+
"ownership", "lineage", "uncertainty",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Claim:
|
|
28
|
+
type: str
|
|
29
|
+
text: str
|
|
30
|
+
confidence: float
|
|
31
|
+
state: str = "supported"
|
|
32
|
+
evidence: list[EvidenceItem] = field(default_factory=list)
|
|
33
|
+
requires_human_confirmation: bool = False
|
|
34
|
+
created_by: str = "machine"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Uncertainty:
|
|
39
|
+
type: str
|
|
40
|
+
text: str
|
|
41
|
+
action: str | None = None
|
|
42
|
+
severity: str = "medium"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ConceptIntelligence:
|
|
47
|
+
concept: ConceptCandidate
|
|
48
|
+
evidence: list[EvidenceItem]
|
|
49
|
+
claims: list[Claim]
|
|
50
|
+
uncertainties: list[Uncertainty]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _has(evidence: list[EvidenceItem], kind: str) -> list[EvidenceItem]:
|
|
54
|
+
return [e for e in evidence if e.signal.kind == kind]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _has_strong_route(evidence: list[EvidenceItem]) -> list[EvidenceItem]:
|
|
58
|
+
return [e for e in evidence if e.kind == "route" and e.strength == "strong"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _concept_presence_claim(concept: ConceptCandidate, evidence: list[EvidenceItem]) -> Claim:
|
|
62
|
+
"""Strength-aware presence claim. Trust Repair (v0.0.6): never say
|
|
63
|
+
'is present' for low-confidence / dependency-only concepts, and never
|
|
64
|
+
contradict the uncertainty section.
|
|
65
|
+
"""
|
|
66
|
+
name = concept.name
|
|
67
|
+
weak_only = getattr(concept, "weak_only", False)
|
|
68
|
+
conf = concept.confidence
|
|
69
|
+
has_behavior = any(
|
|
70
|
+
e.signal.kind in ("route", "auth_dependency", "middleware",
|
|
71
|
+
"webhook_signature_verification", "background_job",
|
|
72
|
+
"token_usage", "queue")
|
|
73
|
+
for e in evidence
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if not weak_only and conf >= 0.75 and has_behavior:
|
|
77
|
+
text = f"{name} is present and supported by behavior evidence."
|
|
78
|
+
state = "supported"
|
|
79
|
+
elif not weak_only and conf >= 0.5:
|
|
80
|
+
text = f"{name} signals are present, but behavior is only partially established."
|
|
81
|
+
state = "partial"
|
|
82
|
+
else:
|
|
83
|
+
text = (
|
|
84
|
+
f"Possible {name} signals detected. "
|
|
85
|
+
f"{name} behavior is not established from current evidence."
|
|
86
|
+
)
|
|
87
|
+
state = "weak"
|
|
88
|
+
return Claim(type="concept", text=text, confidence=conf, state=state, evidence=evidence[:4])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _jwt_claim(name: str, jwt_usage: list[EvidenceItem]) -> Claim:
|
|
92
|
+
"""Purpose-aware JWT claim. Trust Repair (v0.0.6): only assert access-token
|
|
93
|
+
behavior when the evidence shows it; invitation JWTs are not access tokens.
|
|
94
|
+
"""
|
|
95
|
+
purposes = {e.signal.metadata.get("purpose", "unclear") for e in jwt_usage}
|
|
96
|
+
if "access" in purposes:
|
|
97
|
+
return Claim(
|
|
98
|
+
type="usage",
|
|
99
|
+
text=f"{name} uses JWT access tokens.",
|
|
100
|
+
confidence=0.85,
|
|
101
|
+
evidence=jwt_usage,
|
|
102
|
+
)
|
|
103
|
+
if purposes <= {"invitation"}:
|
|
104
|
+
return Claim(
|
|
105
|
+
type="usage",
|
|
106
|
+
text="JWT usage detected for invitations; access-token behavior not established.",
|
|
107
|
+
confidence=0.6,
|
|
108
|
+
state="partial",
|
|
109
|
+
evidence=jwt_usage,
|
|
110
|
+
)
|
|
111
|
+
return Claim(
|
|
112
|
+
type="usage",
|
|
113
|
+
text="JWT usage detected; token purpose not fully classified.",
|
|
114
|
+
confidence=0.55,
|
|
115
|
+
state="partial",
|
|
116
|
+
evidence=jwt_usage,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def generate_claims_and_uncertainty(
|
|
121
|
+
concept: ConceptCandidate, evidence: list[EvidenceItem]
|
|
122
|
+
) -> tuple[list[Claim], list[Uncertainty]]:
|
|
123
|
+
claims: list[Claim] = []
|
|
124
|
+
uncertainties: list[Uncertainty] = []
|
|
125
|
+
|
|
126
|
+
# concept claim - strength-aware (never "is present" for weak evidence).
|
|
127
|
+
if evidence:
|
|
128
|
+
claims.append(_concept_presence_claim(concept, evidence))
|
|
129
|
+
|
|
130
|
+
# behavior claim - active route handling.
|
|
131
|
+
routes = _has_strong_route(evidence)
|
|
132
|
+
if routes:
|
|
133
|
+
claims.append(
|
|
134
|
+
Claim(
|
|
135
|
+
type="behavior",
|
|
136
|
+
text=f"{concept.name} has active route handling.",
|
|
137
|
+
confidence=0.82,
|
|
138
|
+
evidence=routes,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# usage claim - JWT, purpose-aware (usage is not decision).
|
|
143
|
+
jwt_usage = _has(evidence, "token_usage")
|
|
144
|
+
if jwt_usage:
|
|
145
|
+
claims.append(_jwt_claim(concept.name, jwt_usage))
|
|
146
|
+
|
|
147
|
+
# behavior claim - webhook signature verification.
|
|
148
|
+
sig = _has(evidence, "webhook_signature_verification")
|
|
149
|
+
if sig:
|
|
150
|
+
claims.append(
|
|
151
|
+
Claim(
|
|
152
|
+
type="behavior",
|
|
153
|
+
text=f"{concept.name} verifies webhook signatures.",
|
|
154
|
+
confidence=0.85,
|
|
155
|
+
evidence=sig,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# dependency-only presence is weak evidence - never a behavior claim.
|
|
160
|
+
deps = _has(evidence, "dependency")
|
|
161
|
+
if deps and not routes and not sig:
|
|
162
|
+
claims.append(
|
|
163
|
+
Claim(
|
|
164
|
+
type="usage",
|
|
165
|
+
text=f"{concept.name} has related dependencies present.",
|
|
166
|
+
confidence=0.55,
|
|
167
|
+
state="weak",
|
|
168
|
+
evidence=deps[:3],
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# --- Uncertainty generation (uncertainty is output) ---
|
|
173
|
+
|
|
174
|
+
# Presence-only evidence: dependencies/manifests but no parsed behavior.
|
|
175
|
+
if getattr(concept, "weak_only", False):
|
|
176
|
+
uncertainties.append(
|
|
177
|
+
Uncertainty(
|
|
178
|
+
type="presence_only_evidence",
|
|
179
|
+
text=(
|
|
180
|
+
f"Only dependency or manifest evidence was found for "
|
|
181
|
+
f"{concept.name}; presence is not confirmed behavior."
|
|
182
|
+
),
|
|
183
|
+
action=f"Confirm {concept.name} behavior, or treat this as a weak signal.",
|
|
184
|
+
severity="medium",
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Missing decision evidence.
|
|
189
|
+
has_decision = bool(_has(evidence, "decision"))
|
|
190
|
+
if not has_decision:
|
|
191
|
+
uncertainties.append(
|
|
192
|
+
Uncertainty(
|
|
193
|
+
type="missing_decision",
|
|
194
|
+
text=f"No decision was found explaining key choices for {concept.name}.",
|
|
195
|
+
action=f"Record a decision for {concept.name}, or link an existing ADR.",
|
|
196
|
+
severity="medium",
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
claims.append(
|
|
200
|
+
Claim(
|
|
201
|
+
type="uncertainty",
|
|
202
|
+
text=f"No decision was found explaining key choices for {concept.name}.",
|
|
203
|
+
confidence=1.0,
|
|
204
|
+
state="uncertain",
|
|
205
|
+
evidence=[],
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Behavior present but no behavior-specific test.
|
|
210
|
+
behavior_tests = [
|
|
211
|
+
e for e in _has(evidence, "test") if e.strength == "strong"
|
|
212
|
+
]
|
|
213
|
+
if (routes or sig) and not behavior_tests:
|
|
214
|
+
uncertainties.append(
|
|
215
|
+
Uncertainty(
|
|
216
|
+
type="weak_test_evidence",
|
|
217
|
+
text=f"No behavior-specific test was found for {concept.name}.",
|
|
218
|
+
action=f"Add or link a behavior test for {concept.name}.",
|
|
219
|
+
severity="medium",
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return claims, uncertainties
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def build_concept_intelligence(
|
|
227
|
+
concept: ConceptCandidate, evidence: list[EvidenceItem]
|
|
228
|
+
) -> ConceptIntelligence:
|
|
229
|
+
claims, uncertainties = generate_claims_and_uncertainty(concept, evidence)
|
|
230
|
+
return ConceptIntelligence(
|
|
231
|
+
concept=concept,
|
|
232
|
+
evidence=evidence,
|
|
233
|
+
claims=claims,
|
|
234
|
+
uncertainties=uncertainties,
|
|
235
|
+
)
|