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.
Files changed (54) hide show
  1. devtime/__init__.py +9 -0
  2. devtime/ai/__init__.py +0 -0
  3. devtime/ai/local.py +11 -0
  4. devtime/ai/prompts.py +24 -0
  5. devtime/ai/providers.py +41 -0
  6. devtime/assets/devtimeignore.starter +23 -0
  7. devtime/cli.py +374 -0
  8. devtime/config.py +67 -0
  9. devtime/db/__init__.py +0 -0
  10. devtime/db/connection.py +16 -0
  11. devtime/db/migrations.py +114 -0
  12. devtime/db/repository.py +351 -0
  13. devtime/db/schema.sql +145 -0
  14. devtime/fixtures/__init__.py +0 -0
  15. devtime/fixtures/assertions.py +51 -0
  16. devtime/fixtures/loader.py +52 -0
  17. devtime/fixtures/runner.py +73 -0
  18. devtime/intelligence/__init__.py +0 -0
  19. devtime/intelligence/claims.py +235 -0
  20. devtime/intelligence/concepts.py +483 -0
  21. devtime/intelligence/context_pack.py +276 -0
  22. devtime/intelligence/evidence.py +127 -0
  23. devtime/intelligence/lineage.py +21 -0
  24. devtime/intelligence/risk.py +267 -0
  25. devtime/intelligence/scoring.py +99 -0
  26. devtime/mcp/__init__.py +0 -0
  27. devtime/mcp/schemas.py +39 -0
  28. devtime/mcp/server.py +35 -0
  29. devtime/mcp/tools.py +90 -0
  30. devtime/output/__init__.py +0 -0
  31. devtime/output/json_export.py +50 -0
  32. devtime/output/markdown.py +50 -0
  33. devtime/output/terminal.py +208 -0
  34. devtime/paths.py +40 -0
  35. devtime/privacy.py +96 -0
  36. devtime/scanner/__init__.py +0 -0
  37. devtime/scanner/extractors/__init__.py +0 -0
  38. devtime/scanner/extractors/base.py +83 -0
  39. devtime/scanner/extractors/config_files.py +41 -0
  40. devtime/scanner/extractors/docs.py +35 -0
  41. devtime/scanner/extractors/nextjs.py +82 -0
  42. devtime/scanner/extractors/python.py +81 -0
  43. devtime/scanner/extractors/tests.py +61 -0
  44. devtime/scanner/extractors/typescript.py +99 -0
  45. devtime/scanner/file_walker.py +96 -0
  46. devtime/scanner/ignore.py +96 -0
  47. devtime/scanner/language.py +36 -0
  48. devtime/scanner/signals.py +252 -0
  49. devtime_ei-0.1.0.dist-info/METADATA +289 -0
  50. devtime_ei-0.1.0.dist-info/RECORD +54 -0
  51. devtime_ei-0.1.0.dist-info/WHEEL +5 -0
  52. devtime_ei-0.1.0.dist-info/entry_points.txt +2 -0
  53. devtime_ei-0.1.0.dist-info/licenses/LICENSE +201 -0
  54. 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
+ )