devin-qa-pack 0.2.0__tar.gz → 0.3.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.
- {devin_qa_pack-0.2.0/src/devin_qa_pack.egg-info → devin_qa_pack-0.3.0}/PKG-INFO +93 -14
- devin_qa_pack-0.2.0/PKG-INFO → devin_qa_pack-0.3.0/README.md +90 -27
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/pyproject.toml +3 -2
- devin_qa_pack-0.3.0/src/devin_qa_pack/__init__.py +1 -0
- devin_qa_pack-0.3.0/src/devin_qa_pack/adapters/aider.py +138 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/adapters/claude_code.py +22 -1
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/adapters/mcp.py +23 -1
- devin_qa_pack-0.3.0/src/devin_qa_pack/claims.py +340 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/cli.py +111 -2
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/html_report.py +16 -6
- devin_qa_pack-0.3.0/src/devin_qa_pack/install.py +84 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/mcp_server.py +4 -1
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/report.py +24 -7
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/session_end.py +69 -7
- devin_qa_pack-0.3.0/src/devin_qa_pack/sweep.py +93 -0
- devin_qa_pack-0.3.0/src/devin_qa_pack/verify.py +877 -0
- devin_qa_pack-0.2.0/README.md → devin_qa_pack-0.3.0/src/devin_qa_pack.egg-info/PKG-INFO +106 -12
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack.egg-info/SOURCES.txt +6 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack.egg-info/requires.txt +1 -0
- devin_qa_pack-0.3.0/tests/test_action_contract.py +242 -0
- devin_qa_pack-0.3.0/tests/test_adapters.py +670 -0
- devin_qa_pack-0.3.0/tests/test_auto_approve_workflow.py +39 -0
- devin_qa_pack-0.3.0/tests/test_claims.py +350 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_cli.py +15 -2
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_html_report.py +14 -0
- devin_qa_pack-0.3.0/tests/test_install.py +338 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_mcp.py +10 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_offline_core.py +1 -1
- devin_qa_pack-0.3.0/tests/test_release_pypi_workflow.py +49 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_report.py +60 -1
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_session_end.py +118 -0
- devin_qa_pack-0.3.0/tests/test_verify.py +724 -0
- devin_qa_pack-0.2.0/src/devin_qa_pack/__init__.py +0 -1
- devin_qa_pack-0.2.0/src/devin_qa_pack/adapters/aider.py +0 -91
- devin_qa_pack-0.2.0/src/devin_qa_pack/claims.py +0 -204
- devin_qa_pack-0.2.0/src/devin_qa_pack/verify.py +0 -459
- devin_qa_pack-0.2.0/tests/test_adapters.py +0 -315
- devin_qa_pack-0.2.0/tests/test_claims.py +0 -131
- devin_qa_pack-0.2.0/tests/test_verify.py +0 -291
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/setup.cfg +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/adapters/__init__.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/adapters/base.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/adapters/devin.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/evidence/__init__.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/evidence/codex_cli.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/evidence/copilot_cli.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/evidence/devin_cli.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/evidence/vscode_chat.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/intent.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack/paths.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack.egg-info/dependency_links.txt +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack.egg-info/entry_points.txt +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/src/devin_qa_pack.egg-info/top_level.txt +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_evidence.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_evidence_corpus.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_intent.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_paths.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_precommit_hook.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_release_workflow.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_skill.py +0 -0
- {devin_qa_pack-0.2.0 → devin_qa_pack-0.3.0}/tests/test_smoke.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devin-qa-pack
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: QA audit for
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: QA audit for agent sessions: verifies deliverable claims (tests, commits, files, pushes) against recorded tool-call ground truth across Devin CLI, Copilot CLI, Codex CLI and VS Code chat stores.
|
|
5
5
|
Author: Icaro0310
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: devin-internals-spec<0.4.0,>=0.3.0
|
|
10
|
+
Requires-Dist: devin-install-scheduler>=0.1.0
|
|
10
11
|
Provides-Extra: dev
|
|
11
12
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
12
13
|
Requires-Dist: pyyaml>=6; extra == "dev"
|
|
@@ -53,7 +54,8 @@ Part of the [awesome-devin](https://github.com/Icaro0310/awesome-devin) ecosyste
|
|
|
53
54
|
|
|
54
55
|
Flagship QA audit for Devin sessions: checks that what a session *claims* it
|
|
55
56
|
delivered is backed by what its tool calls *actually did* — tests run,
|
|
56
|
-
commits created, files written, work pushed, HTTP statuses returned
|
|
57
|
+
commits created, files written, work pushed, HTTP statuses returned,
|
|
58
|
+
eval scores and coverage numbers reported, work delegated to subagents —
|
|
57
59
|
and prints a verdict per session: `PASS` / `PARTIAL` / `UNVERIFIED`.
|
|
58
60
|
|
|
59
61
|
## The problem
|
|
@@ -81,8 +83,11 @@ Devin persists every tool call in `sessions.db → tool_call_state`. This
|
|
|
81
83
|
tool reads that table via
|
|
82
84
|
[devin-internals-spec](https://github.com/Icaro0310/devin-internals-spec)
|
|
83
85
|
and treats it as **ground truth**: a "tests passed" claim must be backed
|
|
84
|
-
by a run/execute call that ran a test runner and completed; "
|
|
85
|
-
|
|
86
|
+
by a run/execute call that ran a test runner and completed; "47 tests
|
|
87
|
+
passed" must match a recorded `47 passed` in the tool output; "committed
|
|
88
|
+
`sha`" must show the hash in a call or in `git log`; "coverage is at
|
|
89
|
+
87%" or "the eval score was 0.92" must match a recorded percent/score
|
|
90
|
+
in tool-call output. Text-only auditors
|
|
86
91
|
*cannot* do this — remove Devin's store and the check disappears.
|
|
87
92
|
|
|
88
93
|
## Install
|
|
@@ -109,14 +114,25 @@ curl -fsSL https://raw.githubusercontent.com/Icaro0310/devin-assure/main/install
|
|
|
109
114
|
ship the wheel, sdist and CycloneDX SBOM per tag.
|
|
110
115
|
|
|
111
116
|
**See it in action** — run the reproducible
|
|
112
|
-
[Agent Assurance demo](examples/agent-assurance): one command generates a
|
|
117
|
+
[Agent Assurance demo](https://github.com/Icaro0310/devin-assure/tree/main/packages/qa-pack/examples/agent-assurance): one command generates a
|
|
113
118
|
deliberately flawed agent session, then independently verifies what the
|
|
114
119
|
agent actually did. No Devin install needed.
|
|
115
120
|
|
|
116
|
-
<
|
|
121
|
+
<div align="center">
|
|
122
|
+
<a href="https://github.com/Icaro0310/devin-assure/tree/main/packages/qa-pack/examples/agent-assurance"><img src="https://raw.githubusercontent.com/Icaro0310/devin-assure/main/packages/qa-pack/examples/agent-assurance/demo.gif" alt="agent-assurance demo — UNVERIFIED, PARTIAL, PASS" width="80%"/></a>
|
|
123
|
+
</div>
|
|
117
124
|
|
|
118
125
|
## Usage
|
|
119
126
|
|
|
127
|
+
Every command reads local Devin data by default — `sessions.db`, the
|
|
128
|
+
`.devin/` config tree, transcript exports — and never writes back to
|
|
129
|
+
those stores. Two opt-ins do reach the network: `audit --source mcp`
|
|
130
|
+
fetches the session's cloud events, and `--online` probes live URLs.
|
|
131
|
+
Point `--sessions-db` at a copied database to keep reads fully off the
|
|
132
|
+
original store — `session-end` still writes its verdict under the Devin
|
|
133
|
+
data dir (`<data-dir>/qa/`; redirect with `--data-dir` or `--out`).
|
|
134
|
+
Reports are not auto-redacted: excerpt the verdict block before sharing.
|
|
135
|
+
|
|
120
136
|
```bash
|
|
121
137
|
# audit one session (exact id or unique prefix)
|
|
122
138
|
devin-qa-pack audit --session <id> --sessions-db path/to/sessions.db
|
|
@@ -132,6 +148,10 @@ devin-qa-pack report --sessions-db path/to/sessions.db --out report.html
|
|
|
132
148
|
# (hook mode — writes a JSON side file, never touches the session)
|
|
133
149
|
devin-qa-pack session-end
|
|
134
150
|
|
|
151
|
+
# schedule a daily audit sweep over every detected store
|
|
152
|
+
# (cron / Task Scheduler / elapsed registry — opt-in)
|
|
153
|
+
devin-qa-pack install --daily
|
|
154
|
+
|
|
135
155
|
# intent vs. coverage (QA-4): did the agent touch what the prompt named?
|
|
136
156
|
devin-qa-pack intent <session> --sessions-db path/to/sessions.db
|
|
137
157
|
|
|
@@ -148,9 +168,9 @@ devin-qa-pack audit --source mcp --session <cloud-session-id>
|
|
|
148
168
|
Real output, on the synthetic fixture from the quick start:
|
|
149
169
|
|
|
150
170
|
```text
|
|
151
|
-
UNVERIFIED sess-unverifiable Mystery session — 1 claim(s): 0 verified, 0 disputed, 1 unverifiable
|
|
171
|
+
UNVERIFIED sess-unverifiable Mystery session — 1 claim(s): 0 verified, 0 disputed, 1 unverifiable — uncovered: file/docs/spec.pdf
|
|
152
172
|
[unverifiable] file/docs/spec.pdf: no matching tool call and working dir not on disk
|
|
153
|
-
PARTIAL sess-disputed Disputed session — 2 claim(s): 0 verified, 2 disputed, 0 unverifiable
|
|
173
|
+
PARTIAL sess-disputed Disputed session — 2 claim(s): 0 verified, 2 disputed, 0 unverifiable — uncovered: tests/tests, commit/deadbee
|
|
154
174
|
[disputed ] tests/tests: no execute call matching `tests` recorded
|
|
155
175
|
[disputed ] commit/deadbee: no commit call recorded and no repo on disk to check
|
|
156
176
|
PASS sess-verified Verified session — 4 claim(s): 4 verified, 0 disputed, 0 unverifiable
|
|
@@ -166,7 +186,10 @@ Recorded as an asciicast: [assets/demo.cast](assets/demo.cast) —
|
|
|
166
186
|
The `report` subcommand audits all sessions (or one with `--session`,
|
|
167
187
|
bounded with `--limit`) and writes a single deterministic HTML file:
|
|
168
188
|
verdict counts, a per-session table and a per-claim breakdown with
|
|
169
|
-
evidence and the source excerpt for every checked claim.
|
|
189
|
+
evidence and the source excerpt for every checked claim. Verdicts and
|
|
190
|
+
claim statuses render as labeled badges (color + text, never color
|
|
191
|
+
alone) — the report stays self-contained: inline CSS, zero JS, no
|
|
192
|
+
external assets.
|
|
170
193
|
|
|
171
194
|
`--sessions-db` may be omitted. It auto-detects
|
|
172
195
|
`%APPDATA%/devin/cli/sessions.db` on Windows and
|
|
@@ -257,6 +280,14 @@ when the session resolves. A one-line
|
|
|
257
280
|
summary is also printed. `--limit N` bounds the number of claims
|
|
258
281
|
verified.
|
|
259
282
|
|
|
283
|
+
Each run also appends one compact JSON line to `verdicts.jsonl` next to
|
|
284
|
+
the side file (`<data-dir>/qa/verdicts.jsonl` by default) — a
|
|
285
|
+
machine-readable ledger with `session_id`, `verdict`, `audited_at`,
|
|
286
|
+
claim counts, the `uncovered` claim names (disputed/unverifiable
|
|
287
|
+
`kind/detail` list, omitted when empty) and the skip `reason`, for
|
|
288
|
+
dashboards and triggers. The append is best-effort; the side file
|
|
289
|
+
remains the contract.
|
|
290
|
+
|
|
260
291
|
**Fail-soft:** `session-end` always exits `0` once it ran — the verdict
|
|
261
292
|
travels in the side file, so a hook can never fail the host session. A
|
|
262
293
|
session that cannot be resolved produces a `SKIPPED` verdict (still
|
|
@@ -284,6 +315,41 @@ stdin and exports `DEVIN_SESSION_ID`):
|
|
|
284
315
|
}
|
|
285
316
|
```
|
|
286
317
|
|
|
318
|
+
## Scheduled audit sweep (`install --daily`)
|
|
319
|
+
|
|
320
|
+
The `SessionEnd` hook audits live; `devin-qa-pack install --daily` adds
|
|
321
|
+
the complementary **scheduled sweep** — a daily `devin-qa-pack audit
|
|
322
|
+
--all` over every detected surface (`--surface` narrows it) for
|
|
323
|
+
sessions the hook missed (Devin closed early, hook not wired, foreign
|
|
324
|
+
stores). It writes the full payload to
|
|
325
|
+
`<data-dir>/qa/audit-sweep.json` and appends one compact verdict line
|
|
326
|
+
per session (`origin: "sweep"`) to the same
|
|
327
|
+
`<data-dir>/qa/verdicts.jsonl` ledger `session-end` feeds. Read-only on
|
|
328
|
+
every store: the only writes are under the data dir's `qa/` folder, and
|
|
329
|
+
the job does not need Devin closed.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
devin-qa-pack install --daily # auto-detect backend
|
|
333
|
+
devin-qa-pack install --daily --backend cron # pin one backend
|
|
334
|
+
devin-qa-pack install --daily --data-dir /custom/devin --surface devin-cli
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Backend selection follows the shared `devin-install-scheduler`
|
|
338
|
+
convention (same as devin-state's `devin-janitor`/`devin-backup`): a
|
|
339
|
+
tagged `@daily` **crontab** line on Linux/macOS, a daily **Task
|
|
340
|
+
Scheduler** entry on Windows, or — on locked-down machines without
|
|
341
|
+
either — a job record in `<config-dir>/.devin-ecosystem/scheduled.json`
|
|
342
|
+
(the `elapsed` backend) that devin-powerups' `UserPromptSubmit` hook
|
|
343
|
+
ticks (`python tools/schedule.py check --run`). `--backend` pins a
|
|
344
|
+
backend, `--config-dir` relocates the registry, `--data-dir` reshapes
|
|
345
|
+
where the registered command writes, `--json` prints the job record.
|
|
346
|
+
Both `--sessions-db` and `--out` in the registered command are baked to
|
|
347
|
+
absolute paths at install time, so the job does not depend on
|
|
348
|
+
`DEVIN_DATA_DIR`/XDG env surviving into the scheduler's minimal
|
|
349
|
+
environment (re-run `install` if the data dir ever moves).
|
|
350
|
+
The sweep exits `1` when any session is `PARTIAL`/`UNVERIFIED` — the
|
|
351
|
+
verdicts land in the artifacts, the exit code is informational.
|
|
352
|
+
|
|
287
353
|
## Pre-commit gate
|
|
288
354
|
|
|
289
355
|
The repo root ships `.pre-commit-hooks.yaml` — reference it from a
|
|
@@ -313,10 +379,15 @@ in the entry and the scope flags are mutually exclusive — run
|
|
|
313
379
|
`audit --transcript` runs the same claim/evidence model over non-Devin
|
|
314
380
|
transcripts via the adapters in `src/devin_qa_pack/adapters/`:
|
|
315
381
|
|
|
316
|
-
- **`aider`** — `.aider.chat.history.md`.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
382
|
+
- **`aider`** — `.aider.chat.history.md`. `#### /run`, `#### /test`,
|
|
383
|
+
`#### /git` and `#### !cmd` input lines become execute calls with
|
|
384
|
+
`unknown` status (the history does not store exit codes), so action
|
|
385
|
+
claims like "tests passed" resolve `unverifiable`; the `>`-blockquoted
|
|
386
|
+
tool output (`Applied edit to …`, `Commit <sha> …`, recorded test
|
|
387
|
+
output) is pooled as evidence, so value claims — pass counts, HTTP
|
|
388
|
+
statuses, coverage, eval scores — and file/commit details still
|
|
389
|
+
corroborate. Commit claims are also checked against `git log` in the
|
|
390
|
+
working directory.
|
|
320
391
|
- **`claude-code`** — session `.jsonl` transcripts. `Bash` → execute,
|
|
321
392
|
`Write`/`Edit`/`MultiEdit`/`NotebookEdit` → write, `tool_result`
|
|
322
393
|
blocks mark calls completed/failed.
|
|
@@ -384,6 +455,14 @@ network is off by default and the audit is fully offline otherwise.
|
|
|
384
455
|
output: a claim is `verified` when a recorded status matches,
|
|
385
456
|
`disputed` when outputs record a different status and `unverifiable`
|
|
386
457
|
when no output records any status. No request is ever replayed.
|
|
458
|
+
- Counted test claims work the same way: "47 tests passed" is `verified`
|
|
459
|
+
when recorded output reports `47 passed`, `disputed` when it reports
|
|
460
|
+
a different count and `unverifiable` when no output records a count.
|
|
461
|
+
So do coverage and eval-score claims: "coverage is at 87%" needs a
|
|
462
|
+
recorded 87% (coverage.py's `TOTAL` row, a `coverage`/`line-rate`
|
|
463
|
+
payload field, or a "coverage …87%" output string); "the eval score
|
|
464
|
+
was 0.92" needs a recorded `0.92`. Values compare on the scale
|
|
465
|
+
written — an `85%` claim matches a recorded `85`, never `0.85`.
|
|
387
466
|
- File/commit checks use `sessions.working_directory` only when it
|
|
388
467
|
exists on disk and is a git repo — otherwise they rely on tool-call
|
|
389
468
|
evidence alone.
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: devin-qa-pack
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: QA audit for Devin sessions: verifies deliverable claims (tests, commits, files, pushes) against tool_call_state ground truth.
|
|
5
|
-
Author: Icaro0310
|
|
6
|
-
License: MIT
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: devin-internals-spec<0.4.0,>=0.3.0
|
|
10
|
-
Provides-Extra: dev
|
|
11
|
-
Requires-Dist: pytest>=8; extra == "dev"
|
|
12
|
-
Requires-Dist: pyyaml>=6; extra == "dev"
|
|
13
|
-
Provides-Extra: mcp
|
|
14
|
-
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
15
|
-
|
|
16
1
|
<div align="center">
|
|
17
2
|
|
|
18
3
|
<img src="assets/banner.svg" alt="devin-qa-pack" width="100%"/>
|
|
@@ -53,7 +38,8 @@ Part of the [awesome-devin](https://github.com/Icaro0310/awesome-devin) ecosyste
|
|
|
53
38
|
|
|
54
39
|
Flagship QA audit for Devin sessions: checks that what a session *claims* it
|
|
55
40
|
delivered is backed by what its tool calls *actually did* — tests run,
|
|
56
|
-
commits created, files written, work pushed, HTTP statuses returned
|
|
41
|
+
commits created, files written, work pushed, HTTP statuses returned,
|
|
42
|
+
eval scores and coverage numbers reported, work delegated to subagents —
|
|
57
43
|
and prints a verdict per session: `PASS` / `PARTIAL` / `UNVERIFIED`.
|
|
58
44
|
|
|
59
45
|
## The problem
|
|
@@ -81,8 +67,11 @@ Devin persists every tool call in `sessions.db → tool_call_state`. This
|
|
|
81
67
|
tool reads that table via
|
|
82
68
|
[devin-internals-spec](https://github.com/Icaro0310/devin-internals-spec)
|
|
83
69
|
and treats it as **ground truth**: a "tests passed" claim must be backed
|
|
84
|
-
by a run/execute call that ran a test runner and completed; "
|
|
85
|
-
|
|
70
|
+
by a run/execute call that ran a test runner and completed; "47 tests
|
|
71
|
+
passed" must match a recorded `47 passed` in the tool output; "committed
|
|
72
|
+
`sha`" must show the hash in a call or in `git log`; "coverage is at
|
|
73
|
+
87%" or "the eval score was 0.92" must match a recorded percent/score
|
|
74
|
+
in tool-call output. Text-only auditors
|
|
86
75
|
*cannot* do this — remove Devin's store and the check disappears.
|
|
87
76
|
|
|
88
77
|
## Install
|
|
@@ -109,14 +98,25 @@ curl -fsSL https://raw.githubusercontent.com/Icaro0310/devin-assure/main/install
|
|
|
109
98
|
ship the wheel, sdist and CycloneDX SBOM per tag.
|
|
110
99
|
|
|
111
100
|
**See it in action** — run the reproducible
|
|
112
|
-
[Agent Assurance demo](examples/agent-assurance): one command generates a
|
|
101
|
+
[Agent Assurance demo](https://github.com/Icaro0310/devin-assure/tree/main/packages/qa-pack/examples/agent-assurance): one command generates a
|
|
113
102
|
deliberately flawed agent session, then independently verifies what the
|
|
114
103
|
agent actually did. No Devin install needed.
|
|
115
104
|
|
|
116
|
-
<
|
|
105
|
+
<div align="center">
|
|
106
|
+
<a href="https://github.com/Icaro0310/devin-assure/tree/main/packages/qa-pack/examples/agent-assurance"><img src="https://raw.githubusercontent.com/Icaro0310/devin-assure/main/packages/qa-pack/examples/agent-assurance/demo.gif" alt="agent-assurance demo — UNVERIFIED, PARTIAL, PASS" width="80%"/></a>
|
|
107
|
+
</div>
|
|
117
108
|
|
|
118
109
|
## Usage
|
|
119
110
|
|
|
111
|
+
Every command reads local Devin data by default — `sessions.db`, the
|
|
112
|
+
`.devin/` config tree, transcript exports — and never writes back to
|
|
113
|
+
those stores. Two opt-ins do reach the network: `audit --source mcp`
|
|
114
|
+
fetches the session's cloud events, and `--online` probes live URLs.
|
|
115
|
+
Point `--sessions-db` at a copied database to keep reads fully off the
|
|
116
|
+
original store — `session-end` still writes its verdict under the Devin
|
|
117
|
+
data dir (`<data-dir>/qa/`; redirect with `--data-dir` or `--out`).
|
|
118
|
+
Reports are not auto-redacted: excerpt the verdict block before sharing.
|
|
119
|
+
|
|
120
120
|
```bash
|
|
121
121
|
# audit one session (exact id or unique prefix)
|
|
122
122
|
devin-qa-pack audit --session <id> --sessions-db path/to/sessions.db
|
|
@@ -132,6 +132,10 @@ devin-qa-pack report --sessions-db path/to/sessions.db --out report.html
|
|
|
132
132
|
# (hook mode — writes a JSON side file, never touches the session)
|
|
133
133
|
devin-qa-pack session-end
|
|
134
134
|
|
|
135
|
+
# schedule a daily audit sweep over every detected store
|
|
136
|
+
# (cron / Task Scheduler / elapsed registry — opt-in)
|
|
137
|
+
devin-qa-pack install --daily
|
|
138
|
+
|
|
135
139
|
# intent vs. coverage (QA-4): did the agent touch what the prompt named?
|
|
136
140
|
devin-qa-pack intent <session> --sessions-db path/to/sessions.db
|
|
137
141
|
|
|
@@ -148,9 +152,9 @@ devin-qa-pack audit --source mcp --session <cloud-session-id>
|
|
|
148
152
|
Real output, on the synthetic fixture from the quick start:
|
|
149
153
|
|
|
150
154
|
```text
|
|
151
|
-
UNVERIFIED sess-unverifiable Mystery session — 1 claim(s): 0 verified, 0 disputed, 1 unverifiable
|
|
155
|
+
UNVERIFIED sess-unverifiable Mystery session — 1 claim(s): 0 verified, 0 disputed, 1 unverifiable — uncovered: file/docs/spec.pdf
|
|
152
156
|
[unverifiable] file/docs/spec.pdf: no matching tool call and working dir not on disk
|
|
153
|
-
PARTIAL sess-disputed Disputed session — 2 claim(s): 0 verified, 2 disputed, 0 unverifiable
|
|
157
|
+
PARTIAL sess-disputed Disputed session — 2 claim(s): 0 verified, 2 disputed, 0 unverifiable — uncovered: tests/tests, commit/deadbee
|
|
154
158
|
[disputed ] tests/tests: no execute call matching `tests` recorded
|
|
155
159
|
[disputed ] commit/deadbee: no commit call recorded and no repo on disk to check
|
|
156
160
|
PASS sess-verified Verified session — 4 claim(s): 4 verified, 0 disputed, 0 unverifiable
|
|
@@ -166,7 +170,10 @@ Recorded as an asciicast: [assets/demo.cast](assets/demo.cast) —
|
|
|
166
170
|
The `report` subcommand audits all sessions (or one with `--session`,
|
|
167
171
|
bounded with `--limit`) and writes a single deterministic HTML file:
|
|
168
172
|
verdict counts, a per-session table and a per-claim breakdown with
|
|
169
|
-
evidence and the source excerpt for every checked claim.
|
|
173
|
+
evidence and the source excerpt for every checked claim. Verdicts and
|
|
174
|
+
claim statuses render as labeled badges (color + text, never color
|
|
175
|
+
alone) — the report stays self-contained: inline CSS, zero JS, no
|
|
176
|
+
external assets.
|
|
170
177
|
|
|
171
178
|
`--sessions-db` may be omitted. It auto-detects
|
|
172
179
|
`%APPDATA%/devin/cli/sessions.db` on Windows and
|
|
@@ -257,6 +264,14 @@ when the session resolves. A one-line
|
|
|
257
264
|
summary is also printed. `--limit N` bounds the number of claims
|
|
258
265
|
verified.
|
|
259
266
|
|
|
267
|
+
Each run also appends one compact JSON line to `verdicts.jsonl` next to
|
|
268
|
+
the side file (`<data-dir>/qa/verdicts.jsonl` by default) — a
|
|
269
|
+
machine-readable ledger with `session_id`, `verdict`, `audited_at`,
|
|
270
|
+
claim counts, the `uncovered` claim names (disputed/unverifiable
|
|
271
|
+
`kind/detail` list, omitted when empty) and the skip `reason`, for
|
|
272
|
+
dashboards and triggers. The append is best-effort; the side file
|
|
273
|
+
remains the contract.
|
|
274
|
+
|
|
260
275
|
**Fail-soft:** `session-end` always exits `0` once it ran — the verdict
|
|
261
276
|
travels in the side file, so a hook can never fail the host session. A
|
|
262
277
|
session that cannot be resolved produces a `SKIPPED` verdict (still
|
|
@@ -284,6 +299,41 @@ stdin and exports `DEVIN_SESSION_ID`):
|
|
|
284
299
|
}
|
|
285
300
|
```
|
|
286
301
|
|
|
302
|
+
## Scheduled audit sweep (`install --daily`)
|
|
303
|
+
|
|
304
|
+
The `SessionEnd` hook audits live; `devin-qa-pack install --daily` adds
|
|
305
|
+
the complementary **scheduled sweep** — a daily `devin-qa-pack audit
|
|
306
|
+
--all` over every detected surface (`--surface` narrows it) for
|
|
307
|
+
sessions the hook missed (Devin closed early, hook not wired, foreign
|
|
308
|
+
stores). It writes the full payload to
|
|
309
|
+
`<data-dir>/qa/audit-sweep.json` and appends one compact verdict line
|
|
310
|
+
per session (`origin: "sweep"`) to the same
|
|
311
|
+
`<data-dir>/qa/verdicts.jsonl` ledger `session-end` feeds. Read-only on
|
|
312
|
+
every store: the only writes are under the data dir's `qa/` folder, and
|
|
313
|
+
the job does not need Devin closed.
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
devin-qa-pack install --daily # auto-detect backend
|
|
317
|
+
devin-qa-pack install --daily --backend cron # pin one backend
|
|
318
|
+
devin-qa-pack install --daily --data-dir /custom/devin --surface devin-cli
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Backend selection follows the shared `devin-install-scheduler`
|
|
322
|
+
convention (same as devin-state's `devin-janitor`/`devin-backup`): a
|
|
323
|
+
tagged `@daily` **crontab** line on Linux/macOS, a daily **Task
|
|
324
|
+
Scheduler** entry on Windows, or — on locked-down machines without
|
|
325
|
+
either — a job record in `<config-dir>/.devin-ecosystem/scheduled.json`
|
|
326
|
+
(the `elapsed` backend) that devin-powerups' `UserPromptSubmit` hook
|
|
327
|
+
ticks (`python tools/schedule.py check --run`). `--backend` pins a
|
|
328
|
+
backend, `--config-dir` relocates the registry, `--data-dir` reshapes
|
|
329
|
+
where the registered command writes, `--json` prints the job record.
|
|
330
|
+
Both `--sessions-db` and `--out` in the registered command are baked to
|
|
331
|
+
absolute paths at install time, so the job does not depend on
|
|
332
|
+
`DEVIN_DATA_DIR`/XDG env surviving into the scheduler's minimal
|
|
333
|
+
environment (re-run `install` if the data dir ever moves).
|
|
334
|
+
The sweep exits `1` when any session is `PARTIAL`/`UNVERIFIED` — the
|
|
335
|
+
verdicts land in the artifacts, the exit code is informational.
|
|
336
|
+
|
|
287
337
|
## Pre-commit gate
|
|
288
338
|
|
|
289
339
|
The repo root ships `.pre-commit-hooks.yaml` — reference it from a
|
|
@@ -313,10 +363,15 @@ in the entry and the scope flags are mutually exclusive — run
|
|
|
313
363
|
`audit --transcript` runs the same claim/evidence model over non-Devin
|
|
314
364
|
transcripts via the adapters in `src/devin_qa_pack/adapters/`:
|
|
315
365
|
|
|
316
|
-
- **`aider`** — `.aider.chat.history.md`.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
366
|
+
- **`aider`** — `.aider.chat.history.md`. `#### /run`, `#### /test`,
|
|
367
|
+
`#### /git` and `#### !cmd` input lines become execute calls with
|
|
368
|
+
`unknown` status (the history does not store exit codes), so action
|
|
369
|
+
claims like "tests passed" resolve `unverifiable`; the `>`-blockquoted
|
|
370
|
+
tool output (`Applied edit to …`, `Commit <sha> …`, recorded test
|
|
371
|
+
output) is pooled as evidence, so value claims — pass counts, HTTP
|
|
372
|
+
statuses, coverage, eval scores — and file/commit details still
|
|
373
|
+
corroborate. Commit claims are also checked against `git log` in the
|
|
374
|
+
working directory.
|
|
320
375
|
- **`claude-code`** — session `.jsonl` transcripts. `Bash` → execute,
|
|
321
376
|
`Write`/`Edit`/`MultiEdit`/`NotebookEdit` → write, `tool_result`
|
|
322
377
|
blocks mark calls completed/failed.
|
|
@@ -384,6 +439,14 @@ network is off by default and the audit is fully offline otherwise.
|
|
|
384
439
|
output: a claim is `verified` when a recorded status matches,
|
|
385
440
|
`disputed` when outputs record a different status and `unverifiable`
|
|
386
441
|
when no output records any status. No request is ever replayed.
|
|
442
|
+
- Counted test claims work the same way: "47 tests passed" is `verified`
|
|
443
|
+
when recorded output reports `47 passed`, `disputed` when it reports
|
|
444
|
+
a different count and `unverifiable` when no output records a count.
|
|
445
|
+
So do coverage and eval-score claims: "coverage is at 87%" needs a
|
|
446
|
+
recorded 87% (coverage.py's `TOTAL` row, a `coverage`/`line-rate`
|
|
447
|
+
payload field, or a "coverage …87%" output string); "the eval score
|
|
448
|
+
was 0.92" needs a recorded `0.92`. Values compare on the scale
|
|
449
|
+
written — an `85%` claim matches a recorded `85`, never `0.85`.
|
|
387
450
|
- File/commit checks use `sessions.working_directory` only when it
|
|
388
451
|
exists on disk and is a git repo — otherwise they rely on tool-call
|
|
389
452
|
evidence alone.
|
|
@@ -4,14 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devin-qa-pack"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "QA audit for
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "QA audit for agent sessions: verifies deliverable claims (tests, commits, files, pushes) against recorded tool-call ground truth across Devin CLI, Copilot CLI, Codex CLI and VS Code chat stores."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [{ name = "Icaro0310" }]
|
|
13
13
|
dependencies = [
|
|
14
14
|
"devin-internals-spec>=0.3.0,<0.4.0",
|
|
15
|
+
"devin-install-scheduler>=0.1.0",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
[project.optional-dependencies]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Experimental Aider adapter — audits ``.aider.chat.history.md``.
|
|
2
|
+
|
|
3
|
+
Aider records its chat history as markdown (``io.append_chat_history``):
|
|
4
|
+
``####`` lines are user input — natural-language prompts *and* slash
|
|
5
|
+
commands (``#### /run pytest -q``, ``#### !ls``, ``#### /git status``);
|
|
6
|
+
``>`` lines are blockquoted tool/system output (``> Applied edit to
|
|
7
|
+
x.py``, ``> Commit abc1234 ...``, errors, confirmations); everything
|
|
8
|
+
else is assistant prose (the claim source).
|
|
9
|
+
|
|
10
|
+
Evidence is deliberately weak where the format is weak: a recorded
|
|
11
|
+
``/run``/``/test``/``!``/``/git`` line becomes an execute call with
|
|
12
|
+
``unknown`` status — the history does not persist exit codes — so
|
|
13
|
+
action claims like "tests passed" resolve ``unverifiable``, never a
|
|
14
|
+
fake ``verified``. The ``>`` output lines still corroborate
|
|
15
|
+
value-style claims: a recorded "47 passed" backs a ``tests_passed``
|
|
16
|
+
claim, a ``> Commit a1b2c3d`` line backs a ``commit`` claim even
|
|
17
|
+
without the repo on disk (``git log`` in the working directory stays
|
|
18
|
+
the stronger check). Commit claims also verify through the repo's own
|
|
19
|
+
``git log`` whenever the working directory is on disk, since aider
|
|
20
|
+
auto-commits.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from devin_qa_pack.adapters.base import SourceSession
|
|
28
|
+
from devin_qa_pack.claims import claims_from_pairs
|
|
29
|
+
from devin_qa_pack.verify import (
|
|
30
|
+
ParsedToolCall,
|
|
31
|
+
_coverage_values,
|
|
32
|
+
_eval_scores,
|
|
33
|
+
_http_statuses,
|
|
34
|
+
_passed_counts,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
SOURCE = "aider"
|
|
38
|
+
|
|
39
|
+
# Slash commands that actually run a shell command — ``/run``/``/test``
|
|
40
|
+
# shell out and ``/git X`` runs ``git X``. Everything else (``/add``,
|
|
41
|
+
# ``/model``, ``/commit``…) does not execute anything checkable; its
|
|
42
|
+
# evidence, if any, lands on the ``>`` output lines instead.
|
|
43
|
+
_RUN_SLASH = ("/run", "/test", "/git")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _command_of(user_input: str) -> str | None:
|
|
47
|
+
"""Map a ``####`` input line to the shell command it ran, if any.
|
|
48
|
+
|
|
49
|
+
``/run pytest``, ``/test`` and the ``!cmd`` shell shortcut are
|
|
50
|
+
commands; ``/git push`` records as ``git push``. A bare ``/test``
|
|
51
|
+
falls back to aider's configured ``test_cmd`` — unknown here — so
|
|
52
|
+
it records no command.
|
|
53
|
+
"""
|
|
54
|
+
text = user_input.strip()
|
|
55
|
+
if text.startswith("!"):
|
|
56
|
+
command = text[1:].strip()
|
|
57
|
+
return command or None
|
|
58
|
+
for slash in _RUN_SLASH:
|
|
59
|
+
if text == slash or text.startswith(slash + " "):
|
|
60
|
+
rest = text[len(slash):].strip()
|
|
61
|
+
if not rest:
|
|
62
|
+
return None
|
|
63
|
+
return f"git {rest}" if slash == "/git" else rest
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _parse(text: str) -> tuple[list[tuple[str, str]], list[ParsedToolCall]]:
|
|
68
|
+
pairs: list[tuple[str, str]] = []
|
|
69
|
+
calls: list[ParsedToolCall] = []
|
|
70
|
+
output: list[str] = [] # ">" blockquote lines — recorded tool output
|
|
71
|
+
for raw in text.splitlines():
|
|
72
|
+
line = raw.rstrip()
|
|
73
|
+
if not line.strip():
|
|
74
|
+
continue
|
|
75
|
+
if line.startswith("####"):
|
|
76
|
+
user_input = line[4:].strip()
|
|
77
|
+
command = _command_of(user_input)
|
|
78
|
+
if command is not None:
|
|
79
|
+
calls.append(
|
|
80
|
+
ParsedToolCall(
|
|
81
|
+
tool_call_id=f"aider-cmd-{len(calls) + 1}",
|
|
82
|
+
kind="execute",
|
|
83
|
+
status="unknown", # history records no exit code
|
|
84
|
+
commands=(command,),
|
|
85
|
+
search_text=command.lower().replace("\\", "/"),
|
|
86
|
+
http_statuses=(),
|
|
87
|
+
passed_counts=(), # no output on the command line
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
pairs.append(("user", user_input))
|
|
92
|
+
continue
|
|
93
|
+
if line.startswith("#"):
|
|
94
|
+
continue # "# aider chat started at …" and section headers
|
|
95
|
+
if line.startswith(">"):
|
|
96
|
+
output.append(line[1:].strip())
|
|
97
|
+
continue
|
|
98
|
+
pairs.append(("assistant", line))
|
|
99
|
+
if output:
|
|
100
|
+
# Aider's tool output never ties back to a specific command
|
|
101
|
+
# (no ids, no exit codes) — pool it into one evidence call so
|
|
102
|
+
# value claims (counts, statuses, percents, scores) and
|
|
103
|
+
# file/commit details check against what was actually recorded.
|
|
104
|
+
blob = "\n".join(output)
|
|
105
|
+
calls.append(
|
|
106
|
+
ParsedToolCall(
|
|
107
|
+
tool_call_id="aider-output",
|
|
108
|
+
kind="other",
|
|
109
|
+
status="completed",
|
|
110
|
+
commands=(),
|
|
111
|
+
search_text=blob.lower().replace("\\", "/"),
|
|
112
|
+
http_statuses=tuple(sorted(set(_http_statuses(blob)))),
|
|
113
|
+
passed_counts=tuple(sorted(set(_passed_counts(blob)))),
|
|
114
|
+
coverage_values=tuple(sorted(set(_coverage_values(blob)))),
|
|
115
|
+
eval_scores=tuple(sorted(set(_eval_scores(blob)))),
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
return pairs, calls
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load(path: str | Path, working_directory: str | None = None) -> SourceSession:
|
|
122
|
+
"""Load ``.aider.chat.history.md`` into the normalized audit view.
|
|
123
|
+
|
|
124
|
+
``working_directory`` defaults to the directory containing the
|
|
125
|
+
history file — aider writes it at the repository root.
|
|
126
|
+
"""
|
|
127
|
+
p = Path(path).expanduser()
|
|
128
|
+
pairs, calls = _parse(p.read_text(encoding="utf-8", errors="replace"))
|
|
129
|
+
session_id = f"aider:{p.name}"
|
|
130
|
+
return SourceSession(
|
|
131
|
+
source=SOURCE,
|
|
132
|
+
session_id=session_id,
|
|
133
|
+
title=p.name,
|
|
134
|
+
working_directory=working_directory or str(p.parent),
|
|
135
|
+
claims=tuple(claims_from_pairs(pairs, session_id)),
|
|
136
|
+
calls=tuple(calls),
|
|
137
|
+
raw_call_count=len(calls),
|
|
138
|
+
)
|
|
@@ -21,7 +21,14 @@ from typing import Any
|
|
|
21
21
|
|
|
22
22
|
from devin_qa_pack.adapters.base import SourceSession
|
|
23
23
|
from devin_qa_pack.claims import claims_from_pairs
|
|
24
|
-
from devin_qa_pack.verify import
|
|
24
|
+
from devin_qa_pack.verify import (
|
|
25
|
+
ParsedToolCall,
|
|
26
|
+
_coverage_values,
|
|
27
|
+
_eval_scores,
|
|
28
|
+
_http_statuses,
|
|
29
|
+
_passed_counts,
|
|
30
|
+
_strings,
|
|
31
|
+
)
|
|
25
32
|
|
|
26
33
|
SOURCE = "claude-code"
|
|
27
34
|
|
|
@@ -63,7 +70,16 @@ def _result_text(content: Any) -> str:
|
|
|
63
70
|
return "" if content is None else str(content)
|
|
64
71
|
|
|
65
72
|
|
|
73
|
+
# Claude Code's subagent-spawning tools — ``Task`` exactly, plus any
|
|
74
|
+
# custom subagent-named tool — so delegation claims verify against the
|
|
75
|
+
# transcript the same way they do against devin-cli evidence.
|
|
76
|
+
_SPAWN_TOOLS = {"task"}
|
|
77
|
+
|
|
78
|
+
|
|
66
79
|
def _tool_kind(name: str) -> str:
|
|
80
|
+
low = name.lower()
|
|
81
|
+
if low in _SPAWN_TOOLS or "subagent" in low or "sub_agent" in low:
|
|
82
|
+
return "spawn"
|
|
67
83
|
if name in _EXECUTE_TOOLS:
|
|
68
84
|
return "execute"
|
|
69
85
|
if name in _WRITE_TOOLS:
|
|
@@ -117,6 +133,11 @@ def load(path: str | Path, working_directory: str | None = None) -> SourceSessio
|
|
|
117
133
|
http_statuses=tuple(sorted(
|
|
118
134
|
{*_http_statuses(inp), *_http_statuses(output)}
|
|
119
135
|
)),
|
|
136
|
+
# counts come from the tool_result, never the tool_use
|
|
137
|
+
# input (a Write's `content` is the request, not output)
|
|
138
|
+
passed_counts=tuple(sorted(set(_passed_counts(output)))),
|
|
139
|
+
coverage_values=tuple(sorted(set(_coverage_values(output)))),
|
|
140
|
+
eval_scores=tuple(sorted(set(_eval_scores(output)))),
|
|
120
141
|
)
|
|
121
142
|
)
|
|
122
143
|
|
|
@@ -45,7 +45,14 @@ from typing import Any
|
|
|
45
45
|
|
|
46
46
|
from devin_qa_pack.adapters.base import SourceSession
|
|
47
47
|
from devin_qa_pack.claims import claims_from_pairs
|
|
48
|
-
from devin_qa_pack.verify import
|
|
48
|
+
from devin_qa_pack.verify import (
|
|
49
|
+
ParsedToolCall,
|
|
50
|
+
_coverage_values,
|
|
51
|
+
_eval_scores,
|
|
52
|
+
_http_statuses,
|
|
53
|
+
_passed_counts,
|
|
54
|
+
_strings,
|
|
55
|
+
)
|
|
49
56
|
|
|
50
57
|
SOURCE = "mcp"
|
|
51
58
|
|
|
@@ -404,10 +411,19 @@ def load(
|
|
|
404
411
|
]
|
|
405
412
|
texts: list[str] = list(_strings(started)) + outputs
|
|
406
413
|
statuses: set[int] = set(_http_statuses(started))
|
|
414
|
+
joined_output = "".join(outputs)
|
|
415
|
+
# Terminal chunks are one contiguous stdout stream — join before
|
|
416
|
+
# extracting, or a "47 passed" split across updates is missed.
|
|
417
|
+
counts: set[int] = set(_passed_counts(joined_output))
|
|
418
|
+
coverage: set[str] = set(_coverage_values(joined_output))
|
|
419
|
+
scores: set[str] = set(_eval_scores(joined_output))
|
|
407
420
|
status = "unknown"
|
|
408
421
|
if completed is not None:
|
|
409
422
|
texts.extend(_strings(completed))
|
|
410
423
|
statuses |= set(_http_statuses(completed))
|
|
424
|
+
counts |= set(_passed_counts(completed))
|
|
425
|
+
coverage |= set(_coverage_values(completed))
|
|
426
|
+
scores |= set(_eval_scores(completed))
|
|
411
427
|
status = _status_for("shell_process_completed", completed)
|
|
412
428
|
elif started:
|
|
413
429
|
status = "pending"
|
|
@@ -422,6 +438,9 @@ def load(
|
|
|
422
438
|
search_text=" ".join(t for t in texts if t).lower().replace(
|
|
423
439
|
"\\", "/"),
|
|
424
440
|
http_statuses=tuple(sorted(statuses)),
|
|
441
|
+
passed_counts=tuple(sorted(counts)),
|
|
442
|
+
coverage_values=tuple(sorted(coverage)),
|
|
443
|
+
eval_scores=tuple(sorted(scores)),
|
|
425
444
|
))
|
|
426
445
|
|
|
427
446
|
for item in standalone:
|
|
@@ -434,6 +453,9 @@ def load(
|
|
|
434
453
|
commands=_commands(contents),
|
|
435
454
|
search_text=" ".join(texts).lower().replace("\\", "/"),
|
|
436
455
|
http_statuses=tuple(sorted(set(_http_statuses(contents)))),
|
|
456
|
+
passed_counts=tuple(sorted(set(_passed_counts(contents)))),
|
|
457
|
+
coverage_values=tuple(sorted(set(_coverage_values(contents)))),
|
|
458
|
+
eval_scores=tuple(sorted(set(_eval_scores(contents)))),
|
|
437
459
|
))
|
|
438
460
|
|
|
439
461
|
return SourceSession(
|