devtime-ei 0.1.3__tar.gz → 0.2.0__tar.gz

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 (77) hide show
  1. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/PKG-INFO +30 -4
  2. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/README.md +28 -2
  3. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/pyproject.toml +2 -2
  4. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/__init__.py +1 -1
  5. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/cli.py +113 -0
  6. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/db/schema.sql +15 -0
  7. devtime_ei-0.2.0/src/devtime/intelligence/verification.py +464 -0
  8. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/mcp/transport.py +45 -1
  9. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/nextjs.py +14 -2
  10. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/PKG-INFO +30 -4
  11. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/SOURCES.txt +1 -0
  12. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/LICENSE +0 -0
  13. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/setup.cfg +0 -0
  14. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/ai/__init__.py +0 -0
  15. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/ai/local.py +0 -0
  16. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/ai/prompts.py +0 -0
  17. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/ai/providers.py +0 -0
  18. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/assets/devtimeignore.starter +0 -0
  19. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/config.py +0 -0
  20. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/db/__init__.py +0 -0
  21. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/db/connection.py +0 -0
  22. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/db/migrations.py +0 -0
  23. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/db/repository.py +0 -0
  24. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/demo.py +0 -0
  25. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/fixtures/__init__.py +0 -0
  26. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/fixtures/assertions.py +0 -0
  27. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/fixtures/loader.py +0 -0
  28. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/fixtures/runner.py +0 -0
  29. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/__init__.py +0 -0
  30. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/claims.py +0 -0
  31. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/concepts.py +0 -0
  32. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/context_pack.py +0 -0
  33. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/evidence.py +0 -0
  34. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/lineage.py +0 -0
  35. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/risk.py +0 -0
  36. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/intelligence/scoring.py +0 -0
  37. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/mcp/__init__.py +0 -0
  38. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/mcp/schemas.py +0 -0
  39. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/mcp/server.py +0 -0
  40. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/mcp/tools.py +0 -0
  41. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/output/__init__.py +0 -0
  42. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/output/json_export.py +0 -0
  43. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/output/markdown.py +0 -0
  44. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/output/terminal.py +0 -0
  45. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/paths.py +0 -0
  46. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/privacy.py +0 -0
  47. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/.devtimeignore +0 -0
  48. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/README.md +0 -0
  49. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/docs/decisions/0001-use-jwt.md +0 -0
  50. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/package.json +0 -0
  51. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/admin/permissions.ts +0 -0
  52. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/auth/login.ts +0 -0
  53. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/auth/middleware.ts +0 -0
  54. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/auth/tokens.ts +0 -0
  55. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/billing/stripe-webhook.ts +0 -0
  56. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/billing/subscription-service.ts +0 -0
  57. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/export/export-csv.ts +0 -0
  58. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/jobs/email-worker.ts +0 -0
  59. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/src/jobs/queues.ts +0 -0
  60. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/tests/auth-login.test.ts +0 -0
  61. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/resources/demo-saas/tests/stripe-signature.test.ts +0 -0
  62. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/__init__.py +0 -0
  63. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/__init__.py +0 -0
  64. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/base.py +0 -0
  65. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/config_files.py +0 -0
  66. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/docs.py +0 -0
  67. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/python.py +0 -0
  68. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/tests.py +0 -0
  69. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/extractors/typescript.py +0 -0
  70. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/file_walker.py +0 -0
  71. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/ignore.py +0 -0
  72. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/language.py +0 -0
  73. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime/scanner/signals.py +0 -0
  74. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/dependency_links.txt +0 -0
  75. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/entry_points.txt +0 -0
  76. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/requires.txt +0 -0
  77. {devtime_ei-0.1.3 → devtime_ei-0.2.0}/src/devtime_ei.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devtime-ei
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Local-first Engineering Intelligence for software repositories
5
5
  Author-email: Aviad Shakargi <aviad94@gmail.com>
6
6
  Maintainer-email: Aviad Shakargi <aviad94@gmail.com>
@@ -8,7 +8,7 @@ License: Apache-2.0
8
8
  Project-URL: Homepage, https://github.com/Shakargy/devtime
9
9
  Project-URL: Repository, https://github.com/Shakargy/devtime
10
10
  Project-URL: Issues, https://github.com/Shakargy/devtime/issues
11
- Project-URL: Release Notes, https://github.com/Shakargy/devtime/releases/tag/v0.1.3
11
+ Project-URL: Release Notes, https://github.com/Shakargy/devtime/releases/tag/v0.2.0
12
12
  Project-URL: Demo, https://youtu.be/1Hiu3Y9J_SI
13
13
  Keywords: devtools,cli,static-analysis,repository-analysis,engineering-intelligence,local-first
14
14
  Classifier: Development Status :: 3 - Alpha
@@ -204,6 +204,7 @@ Anything outside these six is out of scope for V0. See [LIMITATIONS.md](LIMITATI
204
204
  | `dtc context <concept>` | Create a governed Context Pack for agents or humans. |
205
205
  | `dtc risk --diff` | Review a git diff for risky changes using local evidence (advisory). |
206
206
  | `dtc decision add` | Add a local decision record that can reduce uncertainty. |
207
+ | `dtc verify [claim]` | Verify a repository claim against evidence: status, contradictions, freshness (experimental). |
207
208
 
208
209
  (Also available: `dtc evidence`, `dtc debt`, `dtc status`, `dtc doctor --privacy`,
209
210
  `dtc export`, `dtc reset`, `dtc mcp start`.)
@@ -246,9 +247,10 @@ Or in any MCP client that reads `.mcp.json`:
246
247
  }
247
248
  ```
248
249
 
249
- The agent gets three read-only tools: `list_concepts`, `explain_concept`, and
250
+ The agent gets four read-only tools: `list_concepts`, `explain_concept`,
250
251
  `get_context_pack` (governed context with do-not-change-without-review paths, tests
251
- to run, and agent guidance). Local stdio only - no network listener, no write tools,
252
+ to run, and agent guidance), and `verify_claim` (claim status, contradictions, and
253
+ missing evidence, computed fresh and never persisted). Local stdio only - no network listener, no write tools,
252
254
  no source code returned, only evidence file paths.
253
255
 
254
256
  DevTime is listed in the official MCP Registry as `io.github.Shakargy/devtime`.
@@ -294,6 +296,30 @@ python -m venv .venv
294
296
  pip install -e ".[dev]"
295
297
  ```
296
298
 
299
+ ## Verify claims (experimental)
300
+
301
+ DevTime is growing into a verification layer: ask whether a statement about the
302
+ repository is actually supported.
303
+
304
+ ```bash
305
+ dtc verify billing-webhook-signature
306
+ ```
307
+
308
+ ```
309
+ Billing Webhook Signature Verification
310
+ Claim: Incoming billing webhooks verify the payment provider's signature.
311
+ Status: CONTRADICTED
312
+
313
+ Contradictions:
314
+ - The billing webhook endpoint cannot verify signatures because it is a disabled stub.
315
+ claimed: apps/web/pages/api/stripe/webhook.ts is named and routed as a billing webhook endpoint.
316
+ observed: The handler's only behavior is a 404/501 response.
317
+ ```
318
+
319
+ Statuses are SUPPORTED, WEAK, CONTRADICTED, or UNKNOWN; contradictions always
320
+ show both sides; changed evidence marks a claim STALE. One built-in claim ships
321
+ in v0.2. See **[VERIFICATION.md](VERIFICATION.md)**.
322
+
297
323
  ## Example output
298
324
 
299
325
  ```
@@ -165,6 +165,7 @@ Anything outside these six is out of scope for V0. See [LIMITATIONS.md](LIMITATI
165
165
  | `dtc context <concept>` | Create a governed Context Pack for agents or humans. |
166
166
  | `dtc risk --diff` | Review a git diff for risky changes using local evidence (advisory). |
167
167
  | `dtc decision add` | Add a local decision record that can reduce uncertainty. |
168
+ | `dtc verify [claim]` | Verify a repository claim against evidence: status, contradictions, freshness (experimental). |
168
169
 
169
170
  (Also available: `dtc evidence`, `dtc debt`, `dtc status`, `dtc doctor --privacy`,
170
171
  `dtc export`, `dtc reset`, `dtc mcp start`.)
@@ -207,9 +208,10 @@ Or in any MCP client that reads `.mcp.json`:
207
208
  }
208
209
  ```
209
210
 
210
- The agent gets three read-only tools: `list_concepts`, `explain_concept`, and
211
+ The agent gets four read-only tools: `list_concepts`, `explain_concept`,
211
212
  `get_context_pack` (governed context with do-not-change-without-review paths, tests
212
- to run, and agent guidance). Local stdio only - no network listener, no write tools,
213
+ to run, and agent guidance), and `verify_claim` (claim status, contradictions, and
214
+ missing evidence, computed fresh and never persisted). Local stdio only - no network listener, no write tools,
213
215
  no source code returned, only evidence file paths.
214
216
 
215
217
  DevTime is listed in the official MCP Registry as `io.github.Shakargy/devtime`.
@@ -255,6 +257,30 @@ python -m venv .venv
255
257
  pip install -e ".[dev]"
256
258
  ```
257
259
 
260
+ ## Verify claims (experimental)
261
+
262
+ DevTime is growing into a verification layer: ask whether a statement about the
263
+ repository is actually supported.
264
+
265
+ ```bash
266
+ dtc verify billing-webhook-signature
267
+ ```
268
+
269
+ ```
270
+ Billing Webhook Signature Verification
271
+ Claim: Incoming billing webhooks verify the payment provider's signature.
272
+ Status: CONTRADICTED
273
+
274
+ Contradictions:
275
+ - The billing webhook endpoint cannot verify signatures because it is a disabled stub.
276
+ claimed: apps/web/pages/api/stripe/webhook.ts is named and routed as a billing webhook endpoint.
277
+ observed: The handler's only behavior is a 404/501 response.
278
+ ```
279
+
280
+ Statuses are SUPPORTED, WEAK, CONTRADICTED, or UNKNOWN; contradictions always
281
+ show both sides; changed evidence marks a claim STALE. One built-in claim ships
282
+ in v0.2. See **[VERIFICATION.md](VERIFICATION.md)**.
283
+
258
284
  ## Example output
259
285
 
260
286
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devtime-ei"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "Local-first Engineering Intelligence for software repositories"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -50,7 +50,7 @@ dev = [
50
50
  Homepage = "https://github.com/Shakargy/devtime"
51
51
  Repository = "https://github.com/Shakargy/devtime"
52
52
  Issues = "https://github.com/Shakargy/devtime/issues"
53
- "Release Notes" = "https://github.com/Shakargy/devtime/releases/tag/v0.1.3"
53
+ "Release Notes" = "https://github.com/Shakargy/devtime/releases/tag/v0.2.0"
54
54
  Demo = "https://youtu.be/1Hiu3Y9J_SI"
55
55
 
56
56
  [project.scripts]
@@ -1,6 +1,6 @@
1
1
  """DevTime - local-first Engineering Intelligence for repository memory."""
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.2.0"
4
4
 
5
5
  # Version metadata (Builder Edition, Chapter 20).
6
6
  EVIDENCE_MODEL = "2026.06.1"
@@ -78,6 +78,119 @@ def demo_init(
78
78
  console.print(' dtc explain "Billing Webhooks"')
79
79
 
80
80
 
81
+ @app.command()
82
+ def verify(
83
+ claim: str = typer.Argument(None, help="Claim id to verify. Omit to verify all built-in claims."),
84
+ list_claims: bool = typer.Option(False, "--list", help="List built-in claims with last status and freshness."),
85
+ as_json: bool = typer.Option(False, "--json", help="Stable machine-readable output."),
86
+ ) -> None:
87
+ """Verify a repository claim against scanned evidence (v0.2 experimental)."""
88
+ import json as _json
89
+
90
+ from devtime.db import connection
91
+ from devtime.intelligence import verification as ver
92
+
93
+ if not paths.is_initialized():
94
+ console.print("[red]Not initialized.[/red] Run dtc init first.")
95
+ raise typer.Exit(code=2)
96
+
97
+ conn = connection.connect()
98
+ try:
99
+ if list_claims:
100
+ rows = []
101
+ for slug, definition in ver.BUILTIN_CLAIMS.items():
102
+ latest = ver.load_latest_verification(conn, slug)
103
+ freshness, changed = ver.freshness_for(conn, slug)
104
+ rows.append(
105
+ {
106
+ "claim_id": slug,
107
+ "name": definition.name,
108
+ "statement": definition.statement,
109
+ "last_status": latest[0]["status"] if latest else None,
110
+ "freshness": freshness,
111
+ "changed_evidence": changed,
112
+ }
113
+ )
114
+ if as_json:
115
+ console.print_json(_json.dumps({"schema_version": "1", "claims": rows}))
116
+ else:
117
+ console.print("[bold]Built-in claims[/bold]\n")
118
+ for r in rows:
119
+ status_txt = r["last_status"] or "never verified"
120
+ console.print(f" {r['claim_id']}")
121
+ console.print(f" {r['statement']}")
122
+ console.print(f" last status: {status_txt} freshness: {r['freshness']}")
123
+ for p in r["changed_evidence"]:
124
+ console.print(f" changed since verification: {p}", markup=False)
125
+ console.print("")
126
+ return
127
+
128
+ slugs = [claim] if claim else list(ver.BUILTIN_CLAIMS.keys())
129
+ results = []
130
+ for slug in slugs:
131
+ try:
132
+ result = ver.verify_claim(conn, slug)
133
+ except KeyError:
134
+ console.print(f"[red]Unknown claim:[/red] {slug}")
135
+ console.print("Run [bold]dtc verify --list[/bold] to see built-in claims.")
136
+ raise typer.Exit(code=1)
137
+ ver.save_verification(conn, result)
138
+ results.append(result)
139
+
140
+ if as_json:
141
+ console.print_json(
142
+ _json.dumps(
143
+ {
144
+ "schema_version": "1",
145
+ "command": "verify",
146
+ "results": [r.to_dict() for r in results],
147
+ }
148
+ )
149
+ )
150
+ return
151
+
152
+ for result in results:
153
+ _print_verification(result)
154
+ finally:
155
+ conn.close()
156
+
157
+
158
+ def _print_verification(result) -> None:
159
+ color = {
160
+ "SUPPORTED": "green",
161
+ "WEAK": "yellow",
162
+ "CONTRADICTED": "red",
163
+ "UNKNOWN": "cyan",
164
+ }.get(result.status, "white")
165
+ console.print(f"[bold]{result.claim_name}[/bold]")
166
+ console.print(f"Claim: {result.statement}")
167
+ console.print(f"Status: [{color}]{result.status}[/{color}]")
168
+ console.print("")
169
+ console.print("Why:")
170
+ for line in result.why:
171
+ console.print(f" - {line}", markup=False)
172
+ if result.supporting:
173
+ console.print("\nSupporting evidence:")
174
+ for e in result.supporting[:6]:
175
+ loc = f":{e.start_line}" if e.start_line else ""
176
+ console.print(f" - {e.path}{loc} [{e.strength}]", markup=False)
177
+ console.print(f" {e.observation}", markup=False)
178
+ if result.contradictions:
179
+ console.print("\n[red]Contradictions:[/red]")
180
+ for c in result.contradictions:
181
+ console.print(f" - {c.summary}", markup=False)
182
+ console.print(f" claimed: {c.claimed_side}", markup=False)
183
+ console.print(f" observed: {c.observed_side}", markup=False)
184
+ if result.missing:
185
+ console.print("\nMissing evidence:")
186
+ for m in result.missing:
187
+ console.print(f" - {m}", markup=False)
188
+ console.print("\nLimitations:")
189
+ for lim in result.limitations:
190
+ console.print(f" - {lim}", markup=False)
191
+ console.print("")
192
+
193
+
81
194
  @app.command()
82
195
  def status() -> None:
83
196
  """Show local storage, AI, cloud, telemetry, MCP, and scan status."""
@@ -135,6 +135,21 @@ CREATE TABLE IF NOT EXISTS risk_findings (
135
135
  created_at TEXT NOT NULL
136
136
  );
137
137
 
138
+ -- v0.2.0: immutable claim verification results (the verification slice).
139
+ -- Databases created before v0.2.0 get this table lazily via
140
+ -- verification.ensure_verifications_table (idempotent, no migration needed).
141
+ CREATE TABLE IF NOT EXISTS verifications (
142
+ id TEXT PRIMARY KEY,
143
+ repository_id TEXT NOT NULL,
144
+ claim_slug TEXT NOT NULL,
145
+ status TEXT NOT NULL,
146
+ scan_id TEXT,
147
+ result_json TEXT NOT NULL,
148
+ evidence_fingerprints_json TEXT NOT NULL DEFAULT '[]',
149
+ engine_version TEXT NOT NULL,
150
+ created_at TEXT NOT NULL
151
+ );
152
+
138
153
  CREATE TABLE IF NOT EXISTS context_packs (
139
154
  id TEXT PRIMARY KEY,
140
155
  concept_id TEXT NOT NULL,
@@ -0,0 +1,464 @@
1
+ """Claim verification engine (v0.2.0 - the verification slice).
2
+
3
+ DevTime's evolution target: the verification layer for repository understanding.
4
+ A claim is a statement about the repository; verification evaluates it against
5
+ persisted scan evidence and answers with a status, both-sided contradictions,
6
+ missing evidence, coverage limitations, and freshness - never with confidence
7
+ the evidence cannot back.
8
+
9
+ V0.2 scope, deliberately narrow:
10
+ - Built-in claims only (no user-defined claim files yet).
11
+ - One claim domain: billing webhook signature verification.
12
+ - Four statuses: SUPPORTED, WEAK, CONTRADICTED, UNKNOWN.
13
+ - Freshness from file fingerprints: FRESH, STALE, NEEDS_VERIFICATION.
14
+ - Deterministic and rule-driven. No AI, no network, no code execution.
15
+
16
+ Statuses (documented meaning, per repository evidence policy - not formal proof):
17
+ SUPPORTED required behavior evidence exists in the current scan.
18
+ WEAK the claim's surface exists, but the proving evidence is missing.
19
+ CONTRADICTED credible evidence conflicts with the claim; both sides are shown.
20
+ UNKNOWN the repository shows no relevant surface, or coverage cannot
21
+ responsibly decide.
22
+
23
+ Freshness is separate from truth:
24
+ FRESH supporting evidence files are unchanged since verification.
25
+ STALE at least one evidence file changed or disappeared.
26
+ NEEDS_VERIFICATION the claim has never been verified in this repository.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import sqlite3
33
+ import uuid
34
+ from dataclasses import dataclass, field
35
+ from datetime import datetime, timezone
36
+
37
+ from devtime import __version__
38
+
39
+ # Statuses
40
+ SUPPORTED = "SUPPORTED"
41
+ WEAK = "WEAK"
42
+ CONTRADICTED = "CONTRADICTED"
43
+ UNKNOWN = "UNKNOWN"
44
+
45
+ # Freshness
46
+ FRESH = "FRESH"
47
+ STALE = "STALE"
48
+ NEEDS_VERIFICATION = "NEEDS_VERIFICATION"
49
+
50
+ # Billing vocabulary (kept aligned with concepts.py; duplicated deliberately so
51
+ # the verification engine has no import-time coupling to concept detection).
52
+ _PROVIDER_TOKENS = (
53
+ "stripe", "paypal", "braintree", "chargebee", "lemonsqueezy", "paddle", "razorpay",
54
+ )
55
+ _PAYMENT_TOKENS = _PROVIDER_TOKENS + (
56
+ "billing", "invoice", "payment", "payments", "checkout", "charge",
57
+ )
58
+
59
+
60
+ @dataclass
61
+ class EvidenceRef:
62
+ """One piece of evidence with provenance."""
63
+
64
+ path: str
65
+ observation: str
66
+ kind: str
67
+ strength: str # strong | moderate | weak
68
+ start_line: int | None = None
69
+ end_line: int | None = None
70
+ sha256: str | None = None
71
+
72
+ def to_dict(self) -> dict:
73
+ d = {
74
+ "path": self.path,
75
+ "observation": self.observation,
76
+ "kind": self.kind,
77
+ "strength": self.strength,
78
+ }
79
+ if self.start_line is not None:
80
+ d["start_line"] = self.start_line
81
+ if self.end_line is not None:
82
+ d["end_line"] = self.end_line
83
+ return d
84
+
85
+
86
+ @dataclass
87
+ class Contradiction:
88
+ """A conflict, always with both sides."""
89
+
90
+ summary: str
91
+ claimed_side: str
92
+ observed_side: str
93
+ evidence: list[EvidenceRef] = field(default_factory=list)
94
+
95
+ def to_dict(self) -> dict:
96
+ return {
97
+ "summary": self.summary,
98
+ "claimed_side": self.claimed_side,
99
+ "observed_side": self.observed_side,
100
+ "evidence": [e.to_dict() for e in self.evidence],
101
+ }
102
+
103
+
104
+ @dataclass
105
+ class VerificationResult:
106
+ claim_slug: str
107
+ claim_name: str
108
+ statement: str
109
+ status: str
110
+ why: list[str]
111
+ supporting: list[EvidenceRef]
112
+ contradictions: list[Contradiction]
113
+ missing: list[str]
114
+ limitations: list[str]
115
+ scan_id: str | None
116
+ verified_at: str
117
+ engine_version: str
118
+
119
+ def to_dict(self) -> dict:
120
+ return {
121
+ "schema_version": "1",
122
+ "claim_id": self.claim_slug,
123
+ "claim_name": self.claim_name,
124
+ "statement": self.statement,
125
+ "status": self.status,
126
+ "why": self.why,
127
+ "supporting_evidence": [e.to_dict() for e in self.supporting],
128
+ "contradictions": [c.to_dict() for c in self.contradictions],
129
+ "missing_evidence": self.missing,
130
+ "limitations": self.limitations,
131
+ "scan_id": self.scan_id,
132
+ "verified_at": self.verified_at,
133
+ "engine_version": self.engine_version,
134
+ }
135
+
136
+
137
+ @dataclass(frozen=True)
138
+ class ClaimDefinition:
139
+ slug: str
140
+ name: str
141
+ statement: str
142
+ category: str
143
+
144
+
145
+ BUILTIN_CLAIMS: dict[str, ClaimDefinition] = {
146
+ "billing-webhook-signature": ClaimDefinition(
147
+ slug="billing-webhook-signature",
148
+ name="Billing Webhook Signature Verification",
149
+ statement="Incoming billing webhooks verify the payment provider's signature.",
150
+ category="billing",
151
+ ),
152
+ }
153
+
154
+ _LIMITATIONS = [
155
+ "Heuristic scanner: evidence comes from static patterns, not execution.",
156
+ "Signature verification is recognized for known provider patterns "
157
+ "(e.g. Stripe constructEvent); custom schemes may not be detected.",
158
+ "Coverage follows scanner language support; see LIMITATIONS.md.",
159
+ ]
160
+
161
+
162
+ def _now() -> str:
163
+ return datetime.now(timezone.utc).isoformat()
164
+
165
+
166
+ # --------------------------------------------------------------------------- #
167
+ # Signal loading (persisted scan evidence)
168
+ # --------------------------------------------------------------------------- #
169
+
170
+ def _latest_scan_id(conn: sqlite3.Connection) -> str | None:
171
+ row = conn.execute(
172
+ "SELECT id FROM scans WHERE status = 'completed' "
173
+ "ORDER BY started_at DESC LIMIT 1"
174
+ ).fetchone()
175
+ return row["id"] if row else None
176
+
177
+
178
+ def _load_signals(conn: sqlite3.Connection, scan_id: str) -> list[sqlite3.Row]:
179
+ return conn.execute(
180
+ "SELECT s.kind, s.name, s.value, s.start_line, s.end_line, s.confidence, "
181
+ " s.metadata_json, f.path, f.sha256 "
182
+ "FROM signals s JOIN files f ON f.id = s.file_id "
183
+ "WHERE s.scan_id = ?",
184
+ (scan_id,),
185
+ ).fetchall()
186
+
187
+
188
+ def _hay(row: sqlite3.Row) -> str:
189
+ return " ".join(
190
+ str(x).lower()
191
+ for x in (row["name"], row["value"], row["path"], row["metadata_json"])
192
+ if x is not None
193
+ )
194
+
195
+
196
+ def _is_billingish(hay: str) -> bool:
197
+ return any(t in hay for t in _PAYMENT_TOKENS)
198
+
199
+
200
+ # --------------------------------------------------------------------------- #
201
+ # The engine
202
+ # --------------------------------------------------------------------------- #
203
+
204
+ def verify_claim(conn: sqlite3.Connection, slug: str) -> VerificationResult:
205
+ """Verify one built-in claim against the latest completed scan."""
206
+ definition = BUILTIN_CLAIMS.get(slug)
207
+ if definition is None:
208
+ raise KeyError(slug)
209
+
210
+ scan_id = _latest_scan_id(conn)
211
+ if scan_id is None:
212
+ return VerificationResult(
213
+ claim_slug=definition.slug,
214
+ claim_name=definition.name,
215
+ statement=definition.statement,
216
+ status=UNKNOWN,
217
+ why=["No completed scan exists. Run dtc scan first."],
218
+ supporting=[],
219
+ contradictions=[],
220
+ missing=["A completed repository scan."],
221
+ limitations=_LIMITATIONS,
222
+ scan_id=None,
223
+ verified_at=_now(),
224
+ engine_version=__version__,
225
+ )
226
+
227
+ rows = _load_signals(conn, scan_id)
228
+ return _verify_billing_webhook_signature(definition, rows, scan_id)
229
+
230
+
231
+ def _verify_billing_webhook_signature(
232
+ definition: ClaimDefinition, rows: list[sqlite3.Row], scan_id: str
233
+ ) -> VerificationResult:
234
+ verifications: list[EvidenceRef] = []
235
+ webhook_routes: list[EvidenceRef] = []
236
+ stub_webhooks: list[EvidenceRef] = []
237
+ signature_tests: list[EvidenceRef] = []
238
+ provider_deps: list[EvidenceRef] = []
239
+
240
+ for row in rows:
241
+ hay = _hay(row)
242
+ kind = row["kind"]
243
+
244
+ if kind == "webhook_signature_verification":
245
+ verifications.append(
246
+ _ref(row, "Verifies the provider's webhook signature.", "strong")
247
+ )
248
+ elif kind == "route" and "webhook" in hay and _is_billingish(hay):
249
+ webhook_routes.append(
250
+ _ref(row, "Billing webhook route is handled here.", "moderate")
251
+ )
252
+ elif kind == "disabled_endpoint" and "webhook" in hay and _is_billingish(hay):
253
+ stub_webhooks.append(
254
+ _ref(
255
+ row,
256
+ "Webhook endpoint is a disabled stub: its only behavior is a "
257
+ "404/501 response.",
258
+ "strong",
259
+ )
260
+ )
261
+ elif kind == "test" and "signature" in hay and _is_billingish(hay):
262
+ signature_tests.append(
263
+ _ref(row, "Test exercises webhook signature behavior.", "moderate")
264
+ )
265
+ elif kind == "dependency" and any(p in hay for p in _PROVIDER_TOKENS):
266
+ provider_deps.append(
267
+ _ref(row, "Payment provider dependency is declared.", "weak")
268
+ )
269
+
270
+ why: list[str] = []
271
+ missing: list[str] = []
272
+ contradictions: list[Contradiction] = []
273
+ supporting: list[EvidenceRef] = []
274
+
275
+ if stub_webhooks and not verifications:
276
+ for stub in stub_webhooks:
277
+ contradictions.append(
278
+ Contradiction(
279
+ summary="The billing webhook endpoint cannot verify signatures "
280
+ "because it is a disabled stub.",
281
+ claimed_side=f"{stub.path} is named and routed as a billing "
282
+ "webhook endpoint, which implies webhook handling.",
283
+ observed_side="The handler's only behavior is a 404/501 "
284
+ "response; no webhook processing or signature verification "
285
+ "path exists in this repository snapshot.",
286
+ evidence=[stub],
287
+ )
288
+ )
289
+ why.append(
290
+ "A billing webhook endpoint exists in name, but it is a disabled stub."
291
+ )
292
+ why.append("No signature verification evidence was found anywhere in the scan.")
293
+ status = CONTRADICTED
294
+ supporting = signature_tests # tests may still exist for other editions
295
+ missing.append("An active webhook handler that verifies provider signatures.")
296
+ elif verifications:
297
+ status = SUPPORTED
298
+ supporting = verifications + webhook_routes + signature_tests
299
+ why.append("Signature verification behavior evidence was found.")
300
+ if webhook_routes:
301
+ why.append("Billing webhook route handling was found.")
302
+ if signature_tests:
303
+ why.append("A test exercises signature behavior.")
304
+ else:
305
+ missing.append("A test that exercises webhook signature verification.")
306
+ # A stub existing next to real verification is worth surfacing, not failing.
307
+ for stub in stub_webhooks:
308
+ contradictions.append(
309
+ Contradiction(
310
+ summary="One webhook endpoint is a disabled stub while "
311
+ "verification exists elsewhere.",
312
+ claimed_side=f"{stub.path} is routed as a billing webhook.",
313
+ observed_side="Its only behavior is a 404/501 response.",
314
+ evidence=[stub],
315
+ )
316
+ )
317
+ elif webhook_routes or provider_deps:
318
+ status = WEAK
319
+ supporting = webhook_routes + provider_deps + signature_tests
320
+ why.append(
321
+ "Billing webhook surface exists (routes or provider dependencies), "
322
+ "but no signature verification evidence was found."
323
+ )
324
+ missing.append("Signature verification behavior (e.g. constructEvent).")
325
+ if not signature_tests:
326
+ missing.append("A test that exercises webhook signature verification.")
327
+ else:
328
+ status = UNKNOWN
329
+ why.append(
330
+ "No billing webhook surface was found in the scanned files. "
331
+ "The claim does not apply, or the surface is outside scanner coverage."
332
+ )
333
+ missing.append("Any billing webhook route, handler, or provider dependency.")
334
+
335
+ return VerificationResult(
336
+ claim_slug=definition.slug,
337
+ claim_name=definition.name,
338
+ statement=definition.statement,
339
+ status=status,
340
+ why=why,
341
+ supporting=supporting,
342
+ contradictions=contradictions,
343
+ missing=missing,
344
+ limitations=_LIMITATIONS,
345
+ scan_id=scan_id,
346
+ verified_at=_now(),
347
+ engine_version=__version__,
348
+ )
349
+
350
+
351
+ def _ref(row: sqlite3.Row, observation: str, strength: str) -> EvidenceRef:
352
+ return EvidenceRef(
353
+ path=row["path"],
354
+ observation=observation,
355
+ kind=row["kind"],
356
+ strength=strength,
357
+ start_line=row["start_line"],
358
+ end_line=row["end_line"],
359
+ sha256=row["sha256"],
360
+ )
361
+
362
+
363
+ # --------------------------------------------------------------------------- #
364
+ # Persistence and freshness
365
+ # --------------------------------------------------------------------------- #
366
+
367
+ _VERIFICATIONS_TABLE = """
368
+ CREATE TABLE IF NOT EXISTS verifications (
369
+ id TEXT PRIMARY KEY,
370
+ repository_id TEXT NOT NULL,
371
+ claim_slug TEXT NOT NULL,
372
+ status TEXT NOT NULL,
373
+ scan_id TEXT,
374
+ result_json TEXT NOT NULL,
375
+ evidence_fingerprints_json TEXT NOT NULL DEFAULT '[]',
376
+ engine_version TEXT NOT NULL,
377
+ created_at TEXT NOT NULL
378
+ );
379
+ """
380
+
381
+
382
+ def ensure_verifications_table(conn: sqlite3.Connection) -> None:
383
+ """Idempotent: safe for databases initialized before v0.2.0."""
384
+ conn.execute(_VERIFICATIONS_TABLE)
385
+
386
+
387
+ def save_verification(conn: sqlite3.Connection, result: VerificationResult) -> str:
388
+ """Store an immutable verification result with evidence fingerprints."""
389
+ ensure_verifications_table(conn)
390
+ repo = conn.execute("SELECT id FROM repositories LIMIT 1").fetchone()
391
+ repo_id = repo["id"] if repo else "unknown"
392
+ fingerprints = [
393
+ {"path": e.path, "sha256": e.sha256}
394
+ for e in result.supporting
395
+ if e.sha256
396
+ ]
397
+ # Contradiction evidence participates in freshness too: if the stub changes,
398
+ # the contradiction must be re-checked.
399
+ for c in result.contradictions:
400
+ fingerprints += [
401
+ {"path": e.path, "sha256": e.sha256} for e in c.evidence if e.sha256
402
+ ]
403
+ vid = f"ver-{uuid.uuid4().hex[:10]}"
404
+ conn.execute(
405
+ "INSERT INTO verifications"
406
+ "(id, repository_id, claim_slug, status, scan_id, result_json, "
407
+ " evidence_fingerprints_json, engine_version, created_at) "
408
+ "VALUES (?,?,?,?,?,?,?,?,?)",
409
+ (
410
+ vid,
411
+ repo_id,
412
+ result.claim_slug,
413
+ result.status,
414
+ result.scan_id,
415
+ json.dumps(result.to_dict()),
416
+ json.dumps(fingerprints),
417
+ result.engine_version,
418
+ result.verified_at,
419
+ ),
420
+ )
421
+ conn.commit()
422
+ return vid
423
+
424
+
425
+ def load_latest_verification(
426
+ conn: sqlite3.Connection, slug: str
427
+ ) -> tuple[dict, list[dict], str] | None:
428
+ """Return (result_dict, fingerprints, created_at) for the newest verification."""
429
+ ensure_verifications_table(conn)
430
+ row = conn.execute(
431
+ "SELECT result_json, evidence_fingerprints_json, created_at "
432
+ "FROM verifications WHERE claim_slug = ? ORDER BY created_at DESC LIMIT 1",
433
+ (slug,),
434
+ ).fetchone()
435
+ if row is None:
436
+ return None
437
+ return (
438
+ json.loads(row["result_json"]),
439
+ json.loads(row["evidence_fingerprints_json"]),
440
+ row["created_at"],
441
+ )
442
+
443
+
444
+ def freshness_for(conn: sqlite3.Connection, slug: str) -> tuple[str, list[str]]:
445
+ """Compare stored evidence fingerprints against current file hashes.
446
+
447
+ Returns (freshness, changed_paths). Never flags unrelated file changes:
448
+ only files that were evidence for this claim participate.
449
+ """
450
+ latest = load_latest_verification(conn, slug)
451
+ if latest is None:
452
+ return NEEDS_VERIFICATION, []
453
+ _, fingerprints, _ = latest
454
+ changed: list[str] = []
455
+ for fp in fingerprints:
456
+ row = conn.execute(
457
+ "SELECT sha256 FROM files WHERE path = ? ORDER BY last_seen_scan_id DESC LIMIT 1",
458
+ (fp["path"],),
459
+ ).fetchone()
460
+ if row is None or row["sha256"] != fp["sha256"]:
461
+ changed.append(fp["path"])
462
+ if changed:
463
+ return STALE, sorted(set(changed))
464
+ return FRESH, []
@@ -32,7 +32,12 @@ SERVER_INSTRUCTIONS = (
32
32
  )
33
33
 
34
34
  # The subset of the planned tool surface that is implemented and exposed.
35
- IMPLEMENTED_TOOLS = ("list_concepts", "explain_concept", "get_context_pack")
35
+ IMPLEMENTED_TOOLS = (
36
+ "list_concepts",
37
+ "explain_concept",
38
+ "get_context_pack",
39
+ "verify_claim",
40
+ )
36
41
 
37
42
  _NOT_INITIALIZED = {
38
43
  "error": "not_initialized",
@@ -91,6 +96,45 @@ def build_server():
91
96
  return _NOT_INITIALIZED
92
97
  return tools.get_context_pack(concept, mode=mode)
93
98
 
99
+ @server.tool()
100
+ def verify_claim(claim_id: str = "") -> dict:
101
+ """Verify a repository claim against scanned evidence (read-only compute).
102
+
103
+ Returns status (SUPPORTED / WEAK / CONTRADICTED / UNKNOWN), why,
104
+ supporting evidence with file paths, both-sided contradictions, missing
105
+ evidence, and coverage limitations. Call with no claim_id to list the
106
+ built-in claims. Results are computed fresh and NOT persisted (this
107
+ server stays read-only); use `dtc verify` in a terminal to record one.
108
+ """
109
+ if not paths.is_initialized():
110
+ return _NOT_INITIALIZED
111
+ from devtime.db import connection
112
+ from devtime.intelligence import verification as ver
113
+
114
+ conn = connection.connect()
115
+ try:
116
+ if not claim_id:
117
+ return {
118
+ "builtin_claims": [
119
+ {
120
+ "claim_id": d.slug,
121
+ "name": d.name,
122
+ "statement": d.statement,
123
+ }
124
+ for d in ver.BUILTIN_CLAIMS.values()
125
+ ]
126
+ }
127
+ try:
128
+ result = ver.verify_claim(conn, claim_id)
129
+ except KeyError:
130
+ return {
131
+ "error": "unknown_claim",
132
+ "hint": "Call verify_claim with no claim_id to list built-in claims.",
133
+ }
134
+ return result.to_dict()
135
+ finally:
136
+ conn.close()
137
+
94
138
  return server
95
139
 
96
140
 
@@ -114,9 +114,21 @@ def _extract_pages_api(file: WalkedFile) -> list[Signal]:
114
114
  text = read_text(file)
115
115
  if not _PAGES_HANDLER_RE.search(text):
116
116
  return []
117
- if _is_disabled_stub(text):
118
- return []
119
117
  route_path = derive_pages_route_path(file.rel_path)
118
+ if _is_disabled_stub(text):
119
+ # v0.2.0: a stub is not a route, but it IS a fact worth remembering - the
120
+ # verification engine uses it as contradiction evidence ("the endpoint
121
+ # exists in name; its only behavior is a 404/501"). Concept detection
122
+ # ignores this kind entirely.
123
+ return [
124
+ signal(
125
+ "disabled_endpoint",
126
+ name=f"STUB {route_path}",
127
+ file=file,
128
+ confidence=0.8,
129
+ metadata={"path": route_path, "framework": "nextjs-pages"},
130
+ )
131
+ ]
120
132
  return [
121
133
  signal(
122
134
  "route",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devtime-ei
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Local-first Engineering Intelligence for software repositories
5
5
  Author-email: Aviad Shakargi <aviad94@gmail.com>
6
6
  Maintainer-email: Aviad Shakargi <aviad94@gmail.com>
@@ -8,7 +8,7 @@ License: Apache-2.0
8
8
  Project-URL: Homepage, https://github.com/Shakargy/devtime
9
9
  Project-URL: Repository, https://github.com/Shakargy/devtime
10
10
  Project-URL: Issues, https://github.com/Shakargy/devtime/issues
11
- Project-URL: Release Notes, https://github.com/Shakargy/devtime/releases/tag/v0.1.3
11
+ Project-URL: Release Notes, https://github.com/Shakargy/devtime/releases/tag/v0.2.0
12
12
  Project-URL: Demo, https://youtu.be/1Hiu3Y9J_SI
13
13
  Keywords: devtools,cli,static-analysis,repository-analysis,engineering-intelligence,local-first
14
14
  Classifier: Development Status :: 3 - Alpha
@@ -204,6 +204,7 @@ Anything outside these six is out of scope for V0. See [LIMITATIONS.md](LIMITATI
204
204
  | `dtc context <concept>` | Create a governed Context Pack for agents or humans. |
205
205
  | `dtc risk --diff` | Review a git diff for risky changes using local evidence (advisory). |
206
206
  | `dtc decision add` | Add a local decision record that can reduce uncertainty. |
207
+ | `dtc verify [claim]` | Verify a repository claim against evidence: status, contradictions, freshness (experimental). |
207
208
 
208
209
  (Also available: `dtc evidence`, `dtc debt`, `dtc status`, `dtc doctor --privacy`,
209
210
  `dtc export`, `dtc reset`, `dtc mcp start`.)
@@ -246,9 +247,10 @@ Or in any MCP client that reads `.mcp.json`:
246
247
  }
247
248
  ```
248
249
 
249
- The agent gets three read-only tools: `list_concepts`, `explain_concept`, and
250
+ The agent gets four read-only tools: `list_concepts`, `explain_concept`,
250
251
  `get_context_pack` (governed context with do-not-change-without-review paths, tests
251
- to run, and agent guidance). Local stdio only - no network listener, no write tools,
252
+ to run, and agent guidance), and `verify_claim` (claim status, contradictions, and
253
+ missing evidence, computed fresh and never persisted). Local stdio only - no network listener, no write tools,
252
254
  no source code returned, only evidence file paths.
253
255
 
254
256
  DevTime is listed in the official MCP Registry as `io.github.Shakargy/devtime`.
@@ -294,6 +296,30 @@ python -m venv .venv
294
296
  pip install -e ".[dev]"
295
297
  ```
296
298
 
299
+ ## Verify claims (experimental)
300
+
301
+ DevTime is growing into a verification layer: ask whether a statement about the
302
+ repository is actually supported.
303
+
304
+ ```bash
305
+ dtc verify billing-webhook-signature
306
+ ```
307
+
308
+ ```
309
+ Billing Webhook Signature Verification
310
+ Claim: Incoming billing webhooks verify the payment provider's signature.
311
+ Status: CONTRADICTED
312
+
313
+ Contradictions:
314
+ - The billing webhook endpoint cannot verify signatures because it is a disabled stub.
315
+ claimed: apps/web/pages/api/stripe/webhook.ts is named and routed as a billing webhook endpoint.
316
+ observed: The handler's only behavior is a 404/501 response.
317
+ ```
318
+
319
+ Statuses are SUPPORTED, WEAK, CONTRADICTED, or UNKNOWN; contradictions always
320
+ show both sides; changed evidence marks a claim STALE. One built-in claim ships
321
+ in v0.2. See **[VERIFICATION.md](VERIFICATION.md)**.
322
+
297
323
  ## Example output
298
324
 
299
325
  ```
@@ -29,6 +29,7 @@ src/devtime/intelligence/evidence.py
29
29
  src/devtime/intelligence/lineage.py
30
30
  src/devtime/intelligence/risk.py
31
31
  src/devtime/intelligence/scoring.py
32
+ src/devtime/intelligence/verification.py
32
33
  src/devtime/mcp/__init__.py
33
34
  src/devtime/mcp/schemas.py
34
35
  src/devtime/mcp/server.py
File without changes
File without changes