---
name: feature-ask-evidence-table
description: "Find customer feature asks and workarounds in recorded sales and success calls with Vivu, and build an evidence table with quotes and pages. Use when backing a roadmap item with call evidence."
---Feature ask evidence from recorded customer calls
This skill takes a folder of recorded customer calls (meeting recordings exported as mp4, usually with a screen share) and two to four feature areas a product manager is weighing for the roadmap, and returns evidence.csv: one row per moment where a customer asks for that area or describes working around its absence, with the call file, start and end, a one line paraphrase, the customer's words from the transcript, the speaker label from the meeting tool, the title of the page on the screen share, a frame, and an empty column for the roadmap item. Claude prices the indexing against the user's Vivu plan, uploads the chosen calls to a private Vivu project, runs one search per feature area, checks every hit against the transcript and the frames, and only then writes the table. If the user asks, Claude posts each area's rows as a comment on a Linear issue after showing the comment first.
The value is in what the transcript alone does not hold. Customers say "on this page we can't..." or "here I have to click each one", and which page "here" is only exists on the screen share. Many exported recordings have no transcript at all, and every customer words the same need differently ("pull every order in one go", "export everything in one file", "I run it ten times and paste the files together"), so a keyword search over transcripts misses many of them. Vivu finds the moments by meaning. It does not know who is talking, and in a sales call the rep often describes the very feature the customer wants, so every hit is read against the transcript before it becomes a row, and rep pitches are rejected and counted.
When to use
Use when a product manager, UX researcher or product ops lead says "find every call where customers asked for bulk export", "pull evidence for the permissions roadmap item from our recorded calls", "which customers described a workaround for scheduled reports, and what page were they on", or "build me an evidence table from these four customer calls". For one question about one call ("did they mention SSO in Tuesday's call?"), search Vivu directly. For usability test sessions, where the evidence is what a tester does on screen, use a usability issue skill instead. This skill does not score calls, rate reps or summarize whole calls.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- A moment becomes a row only after the transcript shows the customer saying it and the frame shows the page. Vivu's results are candidates until then; rejected candidates are counted and kept in rejected.csv, not in the table.
- Stop and tell the user when a required capability or file is missing (no transcript for a call, no ffmpeg, no write access in Vivu, no Linear connector). Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: the indexing cost before upload, the sample rows before the full table, and every Linear comment before it is posted.
- Record what customers said. Never judge the rep or the customer success manager on the call, and never infer who is speaking from the voice.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing.
| Requirement | Why | How to check |
|---|---|---|
| Vivu connector with write access | create a private project, open its upload page, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the connection is read only; the user reconnects Vivu and allows write access |
| A shell with ffmpeg and ffprobe on the machine that holds the recordings | measure minutes, extract frames of the screen share | ffmpeg -version and ffprobe -version |
| The call recordings as local mp4 files (exported from Zoom, Meet, Teams or a call recording tool) | frames come from the local files; Vivu has no export tool | ls CALLS_FOLDER |
| A transcript per call with speaker labels where one exists (the meeting tool's VTT or SRT export, or the output of a local speech to text tool) | quotes and speaker labels come from it, never from Vivu's reason text; without it a row keeps quote NOT VERIFIED and speaker UNCERTAIN | ls CALLS_FOLDER for one .vtt or .srt per call; tell the user which calls have none |
| A browser that can open the Vivu upload page | the connector has no direct upload tool | the user opens the link, or Claude in Chrome is connected |
| Linear connector, only if rows should go to Linear issues | Step 7 | list one issue the user names |
The skill needs a shell and local files, so it runs in Claude Code on the user's computer (terminal or the Code tab of Claude Desktop). It downloads nothing from YouTube, so no residential IP is needed, and nothing is scheduled.
Inputs to collect
Ask for anything missing, most important first.
- The feature areas, two to four, each with a key and a plain description (for example bulk_export: exporting all records at once). Required.
- The folder with the calls (CALLS_FOLDER). Required. If there are many, ask for the CRM notes or meeting titles so Step 2 can shortlist.
- Which calls to index. Default: Claude proposes the calls whose titles or CRM notes touch the areas.
- Whether rows go to Linear, and to which issue per area. Default: no, evidence.csv only.
- The Vivu project name. Default: "Call evidence ROADMAP_TOPIC", private, where ROADMAP_TOPIC is a short name for the roadmap question, for example Q4 data features.
Files and state
Keep everything in one working folder next to the calls:
call-evidence/
areas.json feature areas: key, description, query
calls.csv shortlisted calls: file, title or CRM note, why picked, minutes, transcript file
results/ raw search results, one JSON file per area
frames/ full frames of the screen share used for page titles
evidence.csv verified rows
rejected.csv candidates that failed the check, with the reason
state.json uploaded files and video ids, searches run with job ids, every hit with its verdict, rows written, Linear comments posted
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, searches already run are not rerun, checked hits keep their verdicts, and a Linear comment already posted is never posted again, so an interrupted run resumes where it stopped and no moment is written twice.
Step 1: Check the Vivu connector and the setup
Goal: every row of What you need is confirmed before anything is uploaded.
- Call vivu_get_account. If the tool is missing, tell the user to add the Vivu connector in Claude (https://mcp.vivu.ai/mcp) and stop. If can_create_projects is not true, or a write call later fails with "has not granted vivu.write", ask the user to reconnect Vivu with write access and stop until they have.
- Run ffmpeg -version and ffprobe -version. List CALLS_FOLDER and note which calls have a transcript file next to them.
- If Linear is wanted, check the Linear connector by reading one issue the user names.
- Create the working folders; ffmpeg does not create an output folder:
mkdir -p call-evidence/results call-evidence/frames
Run every later command from inside call-evidence/ (cd call-evidence), with CALL_FILE as the path to the recording from there, so frames/ and results/ resolve to the folders just created.
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print versions, and the working folders exist.
Step 2: Confirm consent, write the areas and shortlist the calls
Goal: areas.json and calls.csv, with the user's confirmation that these calls may be used.
- Ask the compliance questions in Compliance items 1 and 2 before anything else in this step. Leave out any call that fails them.
- Write each feature area as a query in the customer's words, covering both the ask and the workaround. Use this shape:
| Field | Query template | Mode | maximum_results |
|---|---|---|---|
| AREA_KEY | the customer says they need AREA_NEED, or describes a workaround such as WORKAROUND because they can't | precise | 20 |
AREA_NEED is the need in plain words (for example "to export or download all of their records at once"), WORKAROUND a way customers get around it (for example "exporting in small batches and stitching the files together"). Name the customer as the subject: the rep describes the same feature in the same calls. All searches are precise because the table needs time ranges; a fast search only returns whole files, and the calls are already shortlisted, so there is no file choice left for fast to make. maximum_results 20 is more than the times a handful of calls usually raise one area, and it is the ceiling on what comes back: a list that stops at exactly 20 means the cap was hit, so raise it and rerun that area. 3. Shortlist from text first. Read the meeting titles and CRM notes and pick the calls that touch the areas. An account's full call archive cannot be indexed on any plan, and most calls never mention a given area. Write calls.csv. 4. Show the areas, the queries and the shortlist, and wait for a yes.
In our test run the calls were made for the test, so the consent question and shortlisting from CRM notes were not exercised in our test run; the areas and queries were written as above.
Done when the user has confirmed consent and approved areas.json and calls.csv.
Step 3: Price the indexing and get approval
Goal: the user sees the cost before anything is uploaded.
- Measure each call and sum the minutes:
ffprobe -v error -show_entries format=duration -of csv=p=0 CALL_FILE
- Call vivu_get_usage for the plan and what remains this month.
- Estimate search credits: one precise search per area, plus one rewording per area in case the first page is not clean. A precise search uses 5 credits in total. Label the total as an estimate.
- Show one table and wait for approval:
| This run | Remaining on the plan | |
|---|---|---|
| Index minutes | measured sum | from vivu_get_usage |
| Search credits | estimate | from vivu_get_usage |
For reference, the Free plan has 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. Four 30 minute calls fit Premium, not Free. If the shortlist does not fit, offer these levers in order: drop calls whose notes only touch an area in passing, split the work across months, and only then a larger plan. Never drop a call the user asked for without saying so.
In our test run the account was an admin account whose vivu_get_usage shows no remaining allowance, so the comparison against a real plan and the approval were not exercised in our test run.
Done when the user has approved the minutes and the credit estimate.
Step 4: Upload and index
Goal: every shortlisted call is ready in a private Vivu project.
- Call vivu_list_projects and reuse the project named in the inputs if it exists. Otherwise call vivu_create_project with that name and visibility "private". Customer calls are sensitive; the default visibility is the whole organization.
- Call vivu_open_upload_page with the project ID right before the upload. The link expires in 180 seconds and is a sign in link, so never paste it into a message or file. Give it to the user to open in their own browser and choose the files from calls.csv, or attach the files with a browser tool that can upload local files. Claude in Chrome accepts at most 10 MB per upload call, and a 30 minute call recording is usually larger; the user adds those in the Vivu web app. Never split or recompress a recording to fit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to calls.csv by file name; Vivu turns spaces and punctuation in names into "_", so compare names with those characters replaced. Record the video IDs in state.json.
In our test run the files went up through a script, not through a user's browser or Claude in Chrome; that upload path was not exercised in our test run.
Done when vivu_list_videos shows every file in calls.csv as ready.
Step 5: Search each area and check every hit
Goal: for each area, a list of checked moments with rejected candidates counted.
Run each area's query with vivu_search_videos (project_id, query, mode "precise", maximum_results 20). It returns a job ID. Call vivu_get_search_results until complete is true; each status call can wait up to 45 seconds, so a pending search is not a stalled one. Save each completed result as results/AREA_KEY.json. Show the result page link in the live reply only; it expires after four hours, so it never goes into evidence.csv, Linear or any file.
Vivu returns a time range that contains the moment, not an exact sentence, and the reason text is a paraphrase, not a transcript. Check every hit:
- Read the transcript lines inside the window (START is start_ms / 1000, END is end_ms / 1000). The hit is real only if a line labeled as the customer asks for the area or describes a workaround. Reject it when the words come from the rep (a demo, a pitch, a roadmap promise), when the customer mentions the area but is satisfied with it, or when nobody talks about the area. Write the reason in state.json.
- When a call has no transcript, the speaker cannot be checked. Keep the hit as a candidate: read vivu_get_video_summary with include_segments, start_ms and end_ms around the window as a second signal, write the row with quote NOT VERIFIED and speaker UNCERTAIN, and tell the user to listen to that stretch. If the user can run a local speech to text tool, check again with its output.
- For every real hit, read the page on the screen share. First make a strip of the start, middle and end of the window, to see whether the page changes inside it (START is start_ms / 1000, DURATION is (end_ms - start_ms) / 1000, MID is DURATION x 25 / 2 and LAST is DURATION x 25 - 13, both rounded to whole frames for a 25 fps recording; use the recording's own frame rate from ffprobe if it differs):
ffmpeg -v error -ss START -t DURATION -i CALL_FILE -vf "select='eq(n\,12)+eq(n\,MID)+eq(n\,LAST)',scale=640:-2,tile=3x1" -frames:v 1 frames/AREA_KEY_HIT_strip.png
HIT is h plus the hit's number within the area (h1, h2, ...).
Then extract one full frame at the middle of the customer's sentence in the transcript (SECONDS) and read the page title on it:
ffmpeg -v error -ss SECONDS -i CALL_FILE -frames:v 1 -q:v 3 frames/AREA_KEY_HIT_read.png
Keep the quotes around the filter; some shells treat the comma list as a pattern otherwise. Read the page title from the full frame, not from the reason text, and never from the window's first frame: windows start a few seconds before the customer speaks, often while the previous page is still up. If the frame shows no readable title, write UNCERTAIN. 4. Rejected hits stay in results/ and go into rejected.csv with the reason; they are counted, not dropped silently. 5. An empty result or a short list does not prove the calls never raise the area. Tell the user, and if transcripts exist, grep them for the area's words (for example grep -n -i "export" TRANSCRIPT_FILE, where TRANSCRIPT_FILE is the call's .vtt or .srt) as a free second pass.
These wordings ran in our test, for three areas; adapt the need and the workaround to the user's areas and keep the shape:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| bulk_export | the customer says they need to export or download all of their records at once, or describes exporting in small batches and stitching the files together because they can't | precise | 20 |
| permissions | the customer says they need finer control over what each user role can see or do, or describes a workaround such as sharing one admin login because they can't restrict access | precise | 20 |
| scheduled_reports | the customer says they want a report sent to people automatically on a schedule, or describes someone running and emailing a report by hand every week | precise | 20 |
Why this chain: the area query finds where customers talk about the need, the transcript turns each window into a checked quote with a speaker, and the frame flips from what was said to what was on the screen share at that moment. The chain has no separate screen search, because the page only matters once a customer moment is found.
Worked example from our test run
In our test run we used 4 synthetic call recordings, 6.1 minutes in total, made for the test: an admin console screen share, a synthetic voice for the sales rep and another for the customer, and a transcript with Rep and Customer labels. Before searching we wrote down every moment: across the three areas there were 12 customer moments (asks and workarounds). Each area also had rep pitches of the same feature on the same pages (a demo of the export screen, a pitch for a bulk export add-on, a roadmap promise of scheduled reports) and a customer mention that was not a request ("assigning the roles was easy"). Some asks happened on an unrelated page, so the screen could not give them away. From the upload to all 4 calls ready took 6.5 minutes, part of it waiting in the queue behind other videos.
The three area queries, with the wordings in the table and no rewording, returned 12 moments: 12 real, 0 false and 0 missed. None of the rep pitches or the satisfied mentions came back, and the asks made on an unrelated page were found like the others, so the matches came from what the customer said, not from the page on screen. The bulk_export query returned 4 moments, 4 real; the permissions query 4 moments, 4 real; the scheduled_reports query 4 moments, 4 real. Median windows were 16.5, 14.5 and 17 seconds, wider than the customer's sentences.
The wording matters. As a control we ran a bulk export query that does not name the customer ("someone talks about exporting a large number of records at once as a single file"). It returned 4 moments: 3 real and 1 false, and 1 missed. The false one was the rep pitching the bulk export add-on, which the transcript check rejected. Name the customer in every query and still check every hit.
Every window started before the customer spoke, and the first frame of the window showed the previous page in all of them; the frame at the customer's sentence gave the right page title every time. The recordings are short and clean, with turns that never overlap and clearly different synthetic voices; real calls with crosstalk, accents, long rambling turns and a rep who paraphrases the customer back were not part of the test, and the transcripts were the script, not a meeting tool export or speech to text output. Those cases were not exercised in our test run.
Done when every hit of every area has a verdict in state.json and the user has seen the true and rejected counts per area.
Step 6: Assemble evidence.csv and confirm the sample
Goal: one table the product manager can sort by area and attach to roadmap items.
- Build one row per verified moment. Narrow start and end to the customer's sentences in the transcript, not the whole Vivu window:
area,call_file,start_mmss,end_mmss,kind,paraphrase,quote,quote_source,speaker_label,page_title,frame_path,status,roadmap_item
bulk_export,CALL_FILE,00:47,00:56,ask,"Wants the full order history exported at once for finance","Honestly, the biggest thing for us is exporting all of our records at once.",meeting transcript,Customer,Dashboard,frames/bulk_export_h2_read.png,verified,
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| area | the area key from areas.json | none |
| call_file | the original file name from calls.csv | none |
| start_mmss, end_mmss | the customer's sentences in the transcript, inside the window | the Vivu window, marked UNCERTAIN |
| kind | ask or workaround, Claude's reading of the transcript (inferred) | UNCERTAIN |
| paraphrase | Claude's one line summary of what the customer wants (inferred) | none |
| quote | the transcript lines, word for word | NOT VERIFIED |
| quote_source | meeting transcript or local speech to text | none |
| speaker_label | the label in the meeting tool's transcript | UNCERTAIN |
| page_title | read from the full frame | UNCERTAIN |
| frame_path | the frame the title was read from | none |
| status | verified | candidate when the transcript is missing |
| roadmap_item | left empty for the product manager | blank |
- Show two or three sample rows and the mapping and wait for a yes before writing the full table. Say which fields are inferred: kind and paraphrase are Claude's reading; if kind is wrong a workaround is counted as an ask, which matters when the team weighs demand.
- Write evidence.csv and rejected.csv, and give the user the counts: rows per area and per call, candidates rejected per area and why.
In our test run the table and rejected.csv were built from the checked hits; the user's approval of the sample rows was not exercised in our test run.
Done when evidence.csv exists with one row per verified moment and the user has approved the sample rows.
Step 7: Post rows to Linear (optional)
Goal: each area's verified rows sit on the issue the product manager chose.
- Only if the user asked for it in the inputs. For each area, render the comment from its verified rows:
Customer call evidence for AREA_KEY (N moments from M calls, checked against transcript and screen share)
- CALL_FILE MM:SS to MM:SS, page PAGE_TITLE: "QUOTE" (KIND)
Use the call file name and the time, never a Vivu result page link (it expires after four hours). Leave out customer company names unless the user says the issue tracker may hold them. 2. Posting to Linear acts as the user. Name the issue, show the full rendered comment, and wait for an explicit yes for each issue. Record each posted comment in state.json so a rerun never posts it twice.
Step 7 was not exercised in our test run: the bulk_export comment was rendered to a file and the Linear connector was never called.
Done when every approved comment is posted and recorded in state.json, or the user chose evidence.csv only.
Compliance
- Use only calls recorded under the company's recording policy, where the customer was told the call is recorded and internal product research is an allowed use. Ask before Step 2 and leave out any call that does not meet this.
- Recordings of customers are personal data. Ask before Step 4 whether the company allows uploading them to a third party service, and create the Vivu project as private. The recordings stay in the user's Vivu project until the user deletes them; the skill never calls vivu_delete_video or vivu_delete_project unless the user asks, and confirms first.
- No face, voice or identity recognition. Vivu is not asked who is speaking; speaker labels come only from the meeting tool's transcript.
- The table records what customers said. It never rates the rep, the customer success manager or the customer.
- Keep customer names out of anything shared beyond the product team. Linear comments are posted only after the user approves each one.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a hit is the rep pitching the feature, not the customer asking for it; the control query that does not name the speaker in our test run returned 1 false of 4, with the reason "The speaker explains that the bulk export add-on enables exporting up to a million rows as a single file." | Vivu searches what is said, not who says it, and reps describe the same features customers ask for | name the customer in the query, and reject any hit whose transcript lines in the window are the rep's |
| (observed) the first frame of the window shows a different page from the one the customer was on | windows start a few seconds before the customer speaks, while the previous page is still up | read the page from a full frame at the customer's sentence in the transcript, never from the window's first frame |
| (observed) a summary segment runs several topics together and skips a customer ask | segments are chapter level; one segment titled "Order history export" covered three topics and did not mention the contractor ask | use segments only as a second signal; the transcript decides |
| (observed) the summary credits a customer's words to the rep | the summary says "the representative confirms the stability of the accounting integration"; in the transcript the customer says it | take speaker labels only from the meeting tool's transcript |
| a call has no transcript, so the speaker cannot be checked | many exported recordings have none | keep the row as a candidate with quote NOT VERIFIED and speaker UNCERTAIN, and ask the user to listen or run local speech to text |
| the reason text quotes the customer with words they did not say | the reason is a paraphrase, not a transcript | copy quotes from the transcript only |
| an area comes back empty or short | an empty result does not prove no customer raised it; the query wording may not match how customers put it | grep the transcripts for the area's words, reword once in the customer's terms, and tell the user |
| a list stops at exactly maximum_results | the cap cut the list short | raise maximum_results and rerun that area |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |
| upload page asks to sign in or shows an error | the one time upload link expires after 180 seconds | request a new link right before opening it |
| a file is rejected by the browser upload tool | Claude in Chrome takes at most 10 MB per upload call | the user adds that file in the Vivu web app; never split or recompress it |
| a result's file name does not match calls.csv | Vivu replaced spaces or punctuation in the name | compare names with those characters replaced by "_" |