Feat/prep and sparring#16
Merged
Merged
Conversation
The next big feature (increment 1). Answers now trace to the candidate's own
résumé/JD/company, and the copilot won't hand them a claim they can't defend.
- answer.ts: the context is already numbered `[1] (resume) …`; the prompt now makes the
model cite those numbers inline after each grounded claim, ground every specific claim
(employer/metric/project) ONLY in context, and — the fabrication guard — when the
context can't support the question, lead with "⚠", say it's not in their background,
and pivot to a cited transferable framing instead of inventing.
- Cue Card: a `Citations` component renders the cited `[i]` as glanceable source chips
(sourceType + match %); clicking one expands the cited chunk — reusing the contextSent
payload already shown in "Data sent to OpenAI".
- Validated the claim→source attribution on the real model before building (the spike
the review flagged): grounded answers cite accurately, and an unsupported question
("led a team of 50") correctly produced "⚠ I haven't led a team of 50 …".
- Tests: answer.test.ts asserts the numbered context + the citation/fabrication-guard
rules in the prompt. typecheck · 79 unit · build · 7 e2e green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generates a study brief before the call: ranked likely questions, coverage gaps (JD asks thin in the résumé) with how-to-bridge lines, strengths to lead with, and company angles. Reuses the existing parsed structures — no re-parse. - brief.ts generateBrief(): one Responses call (parsing model, json_object), defensively defaulted, coverage normalized, fabrication forbidden in-prompt. - jobs:brief IPC gathers profile+job, guards on key/résumé/JD, returns the brief (not persisted — regenerated on demand). Preload + types wired. - Interview page: per-row "Brief" button (disabled without a parsed JD) opens BriefModal, which generates on open. Added a red Badge tone for "missing" gaps. - brief.test.ts (+8): request shape, grounding-only prompt, defensive parsing. - Docs: IPC map, OpenAI service, session log. Verified: typecheck · 87 unit · build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract reusable STAR stories from the résumé, tag them by competency (closed
set), persist them per profile, and index them as `story` chunks so they ground
live answers through the existing retriever (rendered as 📖 source chips).
- New stories table (migration 0005, profile-cascade) + storiesRepo.
- services/openai/stories.ts generateStories(): grounded extraction, defensive
parsing, closed competency vocabulary. Mirrors parsing.ts/brief.ts.
- rag/indexProfile.ts: indexStories + replaceStories. reindexProfile now
excludes sourceType=story so re-saving a résumé won't wipe the curated bank.
- IPC stories.{list,generate,update,delete} + preload; ChunkSource += story;
Cue Card Citations label story chips.
- UI: Story Bank card on the profile editor → StoryBankModal (generate-on-open,
browse/edit/regenerate/delete).
- stories.test.ts (+8).
Hardened per adversarial review (2 confirmed mediums in the regenerate path):
generate() bails on empty extraction, and replaceStories embeds FIRST then
commits rows + chunks + embeddings in one transaction — a failed embed or thin
résumé can no longer wipe or de-ground the bank. indexStories got the same
embed-before-mutate guarantee.
Verified: typecheck · 95 unit · build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A back-and-forth voice drill: the AI interviewer asks aloud (TTS), the candidate
answers by speaking (push-to-talk → record clip → transcribe), and each answer is
coached with a rated, grounded critique. Reuses the existing interviewer/TTS/STT
infra; in-memory only (ephemeral — no DB, no Cue Card).
- feedback.ts evaluateAnswer() → SparringFeedback {verdict, rating 1-5,
strengths, improvements, tip}; grounded + defensively parsed.
- sparringManager.ts: in-memory start/answer/next/end (one active round).
- IPC sparring.{start,answer,next,end} + preload + SparringFeedback type.
- SparringPage + "Sparring" nav item: hold-to-talk (gated until TTS ends),
transcript + feedback, Next, recap. Reuses useAnswerRecorder + MockPage patterns.
- feedback.test.ts (+7).
Hardened per adversarial review (6 confirmed findings, all in the audio/turn
plumbing):
- Mic-stream leak + stuck-recording on the start/stop race: synchronous
recordingRef gate + useAnswerRecorder reconciles a stop that races an in-flight
start (releases the stream); endRecord/finish always stop the recorder.
- Space-key PTT focus-loss: window blur / visibilitychange backstop releases it.
- Playback error left the answer button disabled: onerror clears the audio gate.
- ask() corrupted the turn count on a transient TTS failure: commit to history
only after speak() succeeds.
Verified: typecheck · 102 unit · build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion The Story Bank now pays off in the live interview: the best-matching STAR story for the current question appears as a prominent "📖 Story to tell" callout in the Cue Card (expandable to the full STAR), distinct from the inline citation chips. - rag/retriever.ts: retrieve() embeds the question once and reuses the vector for a new vectorStore.topStory lookup; a story scoring >= STORY_CUE_MIN_SCORE (0.3, @shared/types) is force-included in grounding even if outside the top-k — so it's sent to OpenAI, citable, and rides the existing contextSent payload. No extra embedding call, no new IPC event. - Overlay.tsx StoryCue derives the cue from card.context.chunks (top story >= threshold). - No regression when there are no stories / all below threshold. Verified: typecheck · 102 unit · build green. (Reviewed inline — review-workflow subagents were rate-limited; re-run when the limit resets.) Co-Authored-By: Claude Opus 4.8 (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.
No description provided.