Judge the verdict from the app's perspective - #225
Merged
Merged
Conversation
Extracts src/utils/markdown-query.ts into src/utils/mdq/ as a publish-ready package: MarkdownDoc + Selection, insert/remove verbs alongside query, a comment selector, frontmatter handling, JS-value matchers, and a planned jq-like CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhJNfxLquFknSLJ6V8J9iD
Frontmatter uses yaml's Document API (comment-preserving) rather than a hand-rolled parser; MarkdownDoc gains append/prepend; leading '.' is accepted in the CLI grammar; documents a fourth migration breakage class where MarkdownDoc === string silently stops a guard from firing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhJNfxLquFknSLJ6V8J9iD
11 tasks, 68 steps. Ordering keeps the repo green at every boundary: port behind a shim first, migrate the 11 write-return-type breaks second, then add features additively. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhJNfxLquFknSLJ6V8J9iD
Pilot's verdict prompt defined "fail" purely by goal-shape — "goal not achieved and no further step toward it is available" — while "skipped" was scoped to scenarios irrelevant to the app or to infrastructure failures. A scenario whose premise the app contradicts while behaving correctly had no bucket and landed in "fail", which the session analyst then reports as a product defect. Planner invents scenarios from the UI, so wrong-premise scenarios are a structural class, not an edge case. "fail" now means the app misbehaved; "skipped" now covers a scenario that cannot be judged against this app because the page shows its premise does not hold or the target entity is not the one here. The decision enum's own description is aligned with the same wording. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pilot's verdict prompt defined
"fail"purely by goal-shape — "goal not achieved and no further step toward it is available on the current page" — while"skipped"was scoped to "scenario is irrelevant to the app, OR systematic infrastructure failures". A scenario whose premise the app contradicts while behaving correctly had no bucket, so it landed infail, and the session analyst's FAILED branch (rule 5, "app contradicted expected outcome → Defect") turned it into a reported product defect.Planner invents scenarios from the UI, so wrong-premise scenarios are a structural class, not an edge case.
The session that surfaced it
Langfuse trace
2c854bc971fc8c12834f35bc5d360d5f, scenario "Attempt to create a manual plan without a title and verify that submission is rejected."The form Pilot ended up on labels the field "Title (optional)", and the state it saw reported
disabled buttons: No matched tests, Launch, Save— Save is disabled because no tests are selected, not because Title is empty. The app has no missing-title validation at all. Pilot's own reasoning one turn before the verdict wanted to "finish as a mismatch", then emittedfailbecause that was the nearest available label.Pilot could already set every status —
pass / fail / continue / skippedare all in the schema and each wires toTestResult.*andtask.finish(). The gap was the prompt's vocabulary, not the plumbing.Change
src/ai/pilot.ts—buildVerdictSystemPrompt, plus thedecisionenum's own description so the two agree:"fail"— the app misbehaved: the scenario's action ran against the right target and the app produced a wrong, broken, or missing outcome. Not reaching the goal is not by itself a fail."skipped"— the scenario cannot be judged against this app: the page shows its premise does not hold, the target entity or feature is not the one here, the scenario is irrelevant, or infrastructure failures prevented testing. Still not for "test failed to interact".Trade-off
An app that claims a constraint (required marker, documented validation) but silently accepts invalid input can now read as
skippedinstead offail. The wording mitigates it —failstill applies when the app asserts a constraint and violates it — but it does not eliminate it.Not in this PR
FLOW: create a new manual run, url/runs/new) was offered while the tester was on/plans, and Pilot's first PROGRESS asserted "the manual-run form is already open" on that/planspage. That entity drift is a second, independent cause.session-analyst.ts's "Walk every test" has branches for PASSED and FAILED only — SKIPPED tests fall out of the report rather than being surfaced as untestable premises.Verification
bun test tests/integration/— 148 pass, 1 skip, 0 failbun test tests/unit/— 1436 pass, 0 failbun run format,bun run lint— cleanBehaviour change is prompt-only, so it needs a regression run against a real app to confirm the verdict flips to
skippedon this scenario. Apply theregressionlabel when you want that.🤖 Generated with Claude Code