Cycle 14 of ~15: a UAT file present but empty of headings,
orphans, and placeholders now emits NO_ITEMS_EXTRACTED rather
than reporting passed=false with reasons=[] (which was
indistinguishable from missing-files).
Cycle 13 of ~15: 'result: [pending]' (template placeholder
copy-pasted without being filled) was previously dropped by
the \w+ regex. Now flagged as BRACKETED_PLACEHOLDER.
De-conflicted against ORPHAN_ITEM_MISSING_RESULT so an item
with a bracketed result doesn't double-report.
Cycle 12 of ~15: heading-without-result-line was previously
silently dropped by the regex — meaning a phase with an
unfilled UAT item could falsely pass the predicate. We now
scan for orphan headings and emit ORPHAN_ITEM_MISSING_RESULT
so the operator's typo / unfilled-template is surfaced.
Cycle 11 of ~15: reuses parseVerificationFrontmatterItems from
uat.ts. Frontmatter-declared manual-verification items roll
into the same items[] roster and emit HUMAN_VERIFICATION_NEEDED
reasons so passed=false is justified, not silent.
Cycle 10 of ~15: a captured 'result:' value that lowercases to
'pass' but isn't literally 'pass' now produces a CASE_MISMATCH
reason rather than a generic NON_PASS_RESULT — so operators can
distinguish a real non-pass from a likely typo.
Cycle 9 of ~15: regex now matches both bare 'result:' and
bold-prefixed '**result:**' forms. Requester used the bold
form in the issue text; canonical template uses bare. Both
are equally valid; predicate is form-agnostic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 8 of ~15: adds blockquote-line stripping pass. Quoted
documentation snippets no longer pollute the item roster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 6 of ~15: adds fenced-block stripping pass to
stripMarkdownInjection. Docs-and-examples inside code fences
no longer pollute the item roster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 5 of ~15: introduces stripMarkdownInjection helper.
First pass strips the YAML frontmatter region so injected
'### N. item' patterns inside frontmatter literal blocks
cannot be mistaken for real UAT items.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 4 of ~15: phase dir present but no *-HUMAN-UAT.md files
now returns a typed NO_UAT_FILES reason instead of an empty
reasons array (which prior cycles used as a 'shouldn't happen'
fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 3 of ~15: when resolvePhaseDir returns null, predicate
returns a typed NO_PHASE_DIR reason. Previously the empty
reasons array was indistinguishable from other failure modes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 2 of ~15: introduces REASON_CODE frozen enum and UatReason
typed shape. Non-pass items (result not literally 'pass') now
contribute a typed reason instead of being invisible to callers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 1 of ~15: minimal end-to-end predicate. One phase dir, one
UAT file, one pass result. No injection stripping, no orphan
detection, no CLI registration yet — those follow in subsequent
commits per TDD discipline.
Also exports resolvePhaseDir from phase-list-queries.ts (was
private) so the new predicate can reuse it without duplication.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>