`SourceSpan::locate` in ghostlight-persona-projection was a raw substring
`find`: "I can" byte-exact matched inside "I cannot go" and was accepted as
the quoted span, even though it is not the word the model claims to be
quoting. "Speech is a verbatim span" only holds if the span's edges are word
edges.
`locate` now walks every occurrence of the quote (`match_indices`) and takes
the first one whose start and end each land on a word boundary: the edge of
the source, or a character that is not Unicode-alphanumeric
(`starts_on_word_boundary` / `ends_on_word_boundary`). A first occurrence
that fails either edge is skipped in favor of a later one.
The Interpreter (crates/ghostlight/src/controllers.rs) calls the same
function through `SourceRange`; `cargo test -p ghostlight --lib` still passes
at 603, so no existing test relied on a mid-word span.
Added five tests: a mid-word quote is refused, a whole-word quote is
accepted, a span equal to the whole source is accepted, a later
boundary-valid occurrence is found after an invalid first one (the first
occurrence fails only the start edge), and punctuation at a span's edges is
handled.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>