Skip to content

Feat/prep and sparring#16

Merged
tpikachu merged 5 commits into
masterfrom
feat/prep-and-sparring
Jun 30, 2026
Merged

Feat/prep and sparring#16
tpikachu merged 5 commits into
masterfrom
feat/prep-and-sparring

Conversation

@tpikachu

Copy link
Copy Markdown
Owner

No description provided.

tpikachu and others added 5 commits June 30, 2026 17:04
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>
@tpikachu tpikachu merged commit 0ae607e into master Jun 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant