---
name: stale-guidance-flag-sheet
description: "After a policy or process change, find where recorded training still says, shows or demos the old rule with Vivu, and write a review sheet of timestamps for the content owner."
---Stale guidance flag sheet for recorded training
This skill takes a list of what changed (an old approval limit, a retired form or system, a dropped process step) and the recorded courses that probably mention it, and returns stale_flags.csv: one row per moment where a trainer still says the old rule, a slide still shows it, or a screen demo still uses the old system. Claude shortlists the courses from their titles and outlines, prices the indexing against the user's Vivu plan, uploads the chosen recordings to a private Vivu project, runs two searches per change (one for speech, one for the screen), checks every hit on frames and the local transcript, and writes the sheet with video, change, start and end, where it appears, a frame, the words said, the slide text read from the frame, and an empty status column for the content owner.
The value is in what a transcript cannot hold. Many LMS recordings have no transcript at all, and when they do, it only has the words spoken. A trainer who says "take a moment to read this rule" while the slide shows the old limit leaves nothing in the transcript; a screen demo of the retired expense tool is narrated as "click here, enter the amount". Those moments only exist in the picture. Vivu's screen text search can confuse the old number with the new one or with a nearby number, so every slide hit is read from the frame before it reaches the sheet, and Claude only flags moments: whether each one is outdated is the content owner's call.
When to use
Use when someone in L&D, enablement or training ops says "the approval limit changed, which of our training videos still show the old one?", "find every course that demos the old expense tool", "audit our recorded onboarding for outdated policy", or "make a list of videos we need to re-record after the process change". For one question about one video ("does module 3 mention the old form?"), search Vivu directly. For turning a walkthrough recording into a written runbook, use a runbook skill instead; this one only flags moments for review. It does not rewrite or re-record anything.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- A moment goes on the sheet only after it has been checked against the frames or the local transcript. Vivu's results are candidates until then, and rejected candidates are counted and shown, not written as flags.
- Stop and tell the user when a required capability or file is missing (no transcript for a video, no ffmpeg, no write access in Vivu). Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: the course shortlist before indexing, the indexing cost before upload, and the sample rows before the full sheet.
- Claude flags where the old guidance appears. Whether it is outdated, still acceptable in context, or a compliance problem is decided by the content owner, and the skill never grades the trainer.
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 and contact sheets | ffmpeg -version and ffprobe -version |
| The recordings as local files (exported from the LMS, a shared drive or Zoom cloud recordings) | frames come from the local files; Vivu has no export tool | ls TRAINING_FOLDER |
| The course list with titles and outlines, or the LMS catalog export | picks the courses to index without indexing the whole library | the user pastes it or points to a CSV |
| A transcript per recording where one exists (the LMS caption file as SRT or VTT, or output of a local speech to text tool) | spoken rows quote it, never Vivu's reason text; without it spoken hits stay marked UNCERTAIN, while slide and screen rows still work | ls TRAINING_FOLDER for one .srt or .vtt per video; tell the user which videos 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 |
The skill needs a shell and local files, so it runs in Claude Code on the user's computer. 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 change items, two to four of them, each as old guidance and new guidance (for example: approval limit, old OLD_AMOUNT, new NEW_AMOUNT; expense tool, old OLD_SYSTEM, new NEW_SYSTEM). Required.
- The folder with the recordings (TRAINING_FOLDER) and the course list. Required.
- Which courses to index. Default: Claude proposes a shortlist from titles and outlines in Step 2.
- Where the old guidance could appear. Default: spoken and on screen for every change item; old system screens only for system changes.
- The Vivu project name. Default: "Training review CHANGE_NAME", private.
Files and state
Keep everything in one working folder next to the recordings:
stale-review/
changes.json change items: key, old guidance, new guidance, the spoken and on screen query for each
courses.csv shortlisted courses: file, title, why it was picked, minutes, transcript file
results/ raw search results, one JSON file per query
frames/ contact sheets and full frames used to check hits
stale_flags.csv the review sheet
stale_flags_summary.txt counts per change item, rejected candidates, courses with no flag
state.json uploaded files and video ids, queries run with job ids, every hit with its verdict, rows written
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, queries already run are not rerun, and hits already checked keep their verdicts, 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 the recordings folder and note which videos have a transcript file next to them.
- Create the working folders; ffmpeg does not create an output folder and stops with "Error opening output files: No such file or directory" if it is missing:
mkdir -p stale-review/results stale-review/frames
Run every later command from inside stale-review/ (cd stale-review), with VIDEO_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: Write the change items and shortlist the courses
Goal: changes.json with two to four change items and courses.csv with only the courses that can mention them.
- For each change item, write the old guidance the way a trainer would put it on a slide and the way they would say it. Numbers are the part that goes wrong, so write the old number exactly as the slides print it (OLD_AMOUNT such as "$500") and as it is spoken ("five hundred dollars"), and write down the new number and any other amounts in the same policy (a higher approval tier, a per diem) as look-alikes to watch for.
- Build two queries per change item, in plain words:
| Field | Query template | Mode |
|---|---|---|
| CHANGE_slide | a slide or on-screen text saying OLD_RULE_AS_WRITTEN | precise |
| CHANGE_spoken | the trainer mentions out loud OLD_RULE_AS_SPOKEN | precise |
| CHANGE_ui (system changes only) | a screen demo of the old OLD_SYSTEM TOOL_KIND | precise |
OLD_RULE_AS_WRITTEN is the old rule with the number in digits, OLD_RULE_AS_SPOKEN the same rule with the number in words and every noun a trainer might hang it on (for example "the five hundred dollar limit above which an expense or report needs a manager to approve it"), OLD_SYSTEM the retired system's name as it appears in its window title, TOOL_KIND what it is (expense tool, CRM, ticketing system). Why this chain: the spoken query finds where the trainer says the old rule, the slide query flips to where it is shown but maybe never said, and the UI query covers the old system that is only ever shown. All three are precise because the sheet needs time ranges; a fast search only returns whole files. 3. Read the course list (titles, outlines, module descriptions). Pick the courses whose outline touches a changed area: approvals, expenses, purchasing, the retired system. A library of several hundred hours cannot be indexed on any plan, and most of it never mentions the change. Write courses.csv with file, title, why picked, and transcript file. 4. Show the change items, the queries and the shortlist to the user and wait for a yes.
In our test run the courses were made for the test, so shortlisting from a real course catalog was not exercised in our test run; the change items and queries were written as above.
Done when the user has approved changes.json and courses.csv.
Step 3: Price the indexing and get approval
Goal: the user sees what the shortlist costs before anything is uploaded.
- Measure each file and sum the minutes:
ffprobe -v error -show_entries format=duration -of csv=p=0 VIDEO_FILE
- Call vivu_get_usage for the plan and what remains this month.
- Estimate search credits: two or three precise searches per change item. A precise search uses 5 credits in total; label the total as an estimate, since rewording a query and running it again adds to it.
- Show one table and wait for approval:
| This review | 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, which covers one course of about 20 minutes; Premium is $30 a month with 180 index minutes and 500 search credits. If the shortlist does not fit, offer these levers in order: drop courses whose outline only touches the change in passing, split the review across months starting with the courses learners see most, and only then a larger plan. Never drop a course 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 recording 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". Internal training often shows internal systems and people; 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 do not paste it into any message or file. Give it to the user to open in their own browser and choose the files from courses.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 20 minute course is usually larger; the user adds those in the Vivu web app. Never split or recompress a recording to fit a tool limit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to courses.csv by file name; Vivu turns spaces and punctuation in names into the "_" character, 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 courses.csv as ready.
Step 5: Run the searches and check every hit
Goal: for each change item, a list of checked moments, with rejected candidates counted.
Run each query from changes.json 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/FIELD.json, where FIELD is the query's field name from changes.json (for example approval_limit_slide) and VIDEO_FILE below is the local recording the hit points to. Show the result page link in the live reply only; it expires after four hours, so it never goes into stale_flags.csv or any file. maximum_results 20 is more than the times a shortlisted course normally mentions one rule, and it is also the ceiling on what comes back: a list that stops at exactly 20 means the cap was hit, so raise it and rerun that query.
Vivu returns a time range that contains the moment, not an exact frame, and the reason text is a paraphrase, not a transcript. Check every hit before it goes anywhere:
- For slide and UI hits, make a contact sheet of the whole window at two frames a second (START is start_ms / 1000, DURATION is (end_ms - start_ms) / 1000, N is the hit number, ROWS is DURATION x 2 / 6 rounded up):
ffmpeg -v error -ss START -t DURATION -i VIDEO_FILE -vf "fps=2,scale=320:-2,tile=6xROWS" -frames:v 1 frames/FIELD_hN_sheet.png
ROWS follows the window length (a 16 second window needs 6 rows, a 22 second window needs 8), so the last frames of the window, where the next slide can appear, are on the sheet. A fixed grid that is too small leaves the end of the window off the sheet.
Find the frames where the slide or the old screen is up, then extract one full size frame from that stretch (SECONDS is a time inside it) and read the text on it:
ffmpeg -v error -ss SECONDS -i VIDEO_FILE -frames:v 1 -q:v 3 frames/FIELD_hN_read.png
Keep the quotes around the filter; some shells treat the comma list as a pattern otherwise. 2. A slide hit is real only if the full frame shows the old guidance as written in changes.json, with the old number. Screen text reasons have described text that was not on screen in other Vivu tests (a price table on a page with no prices, a label that was not there), so the number is always read from the frame: a slide with the new number, a higher approval tier or another amount is rejected even if the reason names the old one. Look at the first and last frames of the window too, because a window can take in part of the next slide. Copy the slide text onto the sheet from the frame, never from the reason. 3. A UI hit is real only if the frame shows the old system's own window (its title bar, its menus), not the new system and not a slide about either. 4. For spoken hits, read the transcript lines inside the window. The hit is real only if the trainer states the old rule in words; the new rule, or the old rule shown on a slide while the trainer says "read this", does not count for the spoken field. Copy the words onto the sheet from the transcript. A video with no transcript keeps its spoken hits as candidates: write them with where set to "spoken, UNCERTAIN: no transcript" and said_local blank, so the content owner listens to that stretch before deciding; if the user can make a transcript with a local speech to text tool, check again after. As a second signal, vivu_get_video_summary with include_segments, start_ms and end_ms around the window shows what the section is about; its section boundaries are chapter level, so it never replaces the transcript. 5. Record every verdict in state.json with a short reason. Rejected hits stay in results/ and are counted, not written to the sheet. 6. An empty result does not prove a course never mentions the change. For courses on the shortlist with no checked hit, tell the user, and offer a contact sheet of the slides at one frame every two seconds for a quick look by eye. 7. If transcripts exist, also search them for the old rule with grep, with the number in digits and in words (for example grep -n -i "five hundred" TRANSCRIPT_FILE, where TRANSCRIPT_FILE is the video's .srt or .vtt). This is free and catches sentences the spoken search misses; the spoken search still matters for recordings without a transcript and for paraphrases the grep does not anticipate.
These are the wordings that ran in the test described below, for one limit change and one system change. Adapt the rule, the number and the system name to the user's change items and keep the shape:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| approval_limit_slide | a slide or on-screen text saying manager approval is required for expenses over $500 | precise | 20 |
| approval_limit_spoken | the trainer mentions out loud the five hundred dollar limit above which an expense or report needs a manager to approve it | precise | 20 |
| expense_tool_ui | a screen demo of the old ClaimDesk Classic expense tool | precise | 20 |
The spoken wording names the number and both "expense" and "report", because the first, narrower wording ("expenses over five hundred dollars need a manager's approval") missed a trainer talking about approving a report above the limit.
Worked example from our test run
In our test run we used 4 synthetic training recordings, 3.2 minutes in total, made for the test with slides, a synthetic narrator and screen demos: an onboarding module, a manager course, a finance update that only shows the new rules, and a travel refresher where an old slide is followed by a look-alike slide with the new limit. The change items were a raised approval limit and a retired expense tool. We wrote down every place the old guidance appears before searching. The narration over the old slides never reads the number aloud, so the slide search could only succeed from the screen, and the slides shown while the old rule is spoken carry no number. From the upload to all 4 recordings ready took 3.3 minutes.
The slide query returned 3 moments: 3 real, 0 false and 0 missed. It returned none of the new limit slides, the higher approval tier slide or the per diem slide. The median window was 16 seconds, wider than the slides themselves; every window started on the course title card, and the travel refresher's window ran into the following slide with the new limit. That is why the sheet takes its times and text from the contact sheet and the full frame.
The spoken query first returned 1 moment, 1 real, 0 false and 1 missed. After 1 rewording on the same corpus it returned 2 moments: 2 real, 0 false and 0 missed, with a median window of 13.5 seconds. The new limit spoken aloud was never returned. The summary segments around both windows described the slide on screen and did not mention the spoken rule, so spoken hits are confirmed from the transcript, not from the summary.
The UI query returned 1 moment: 1 real, 0 false and 0 missed; the new tool's demo, with the same narration and the same clicks, was not returned. The old tool's name is printed in its title bar, so this did not test recognizing an old screen that carries no name.
The slides in the test are large, clean text on plain backgrounds. Busy slides, small print, screen recordings at low resolution and a real library of long courses were not part of the test, and the transcripts were the narrator's script rather than LMS captions or local speech to text output, so those cases were not exercised in our test run.
Done when every hit of every query has a verdict in state.json and the user has seen the true and false counts per query.
Step 6: Assemble stale_flags.csv and confirm the sample
Goal: the content owner gets one sheet, grouped by video and ordered by time.
- Build one row per checked moment. Narrow start and end to what the contact sheet and transcript show (the slide on screen, the sentence spoken), not the whole Vivu window:
video,change_item,start_mmss,end_mmss,where,frame,said_local,screen_text_from_frame,status
expenses_module3.mp4,approval_limit,00:05,00:17,slide,frames/approval_limit_slide_h1_read.png,,"Manager approval required for any expense over OLD_AMOUNT.",
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| video | the original file name from courses.csv | none |
| change_item | the change key from changes.json | none |
| start_mmss, end_mmss | checked start and end inside the window, as MM:SS | none |
| where | spoken, slide or ui, from the query that found it and what the check confirmed | none |
| frame | path of the full frame read | blank for spoken rows |
| said_local | transcript lines inside the window | blank for slide and ui rows |
| screen_text_from_frame | read from the full frame | blank for spoken rows |
| status | left empty for the content owner: outdated, still OK, or needs discussion | blank |
- When one window holds both the old slide and the trainer saying the old rule, write two rows (slide and spoken), because the fixes differ: a slide can be patched, a spoken line needs a re-record or an edit.
- Show the user two or three sample rows and the mapping, and wait for a yes before writing the full sheet. Say which fields are inferred: where the slide ends is Claude's reading of the contact sheet, and if it is wrong the content owner jumps to a slightly wrong time.
- Write stale_flags.csv, and next to it stale_flags_summary.txt with the counts: checked flags per change item and per video, candidates rejected per query, and shortlisted courses with no flag. Hand the file to the user; the skill does not send it anywhere.
In our test run the sheet and the summary were built from the checked hits; the user's approval of the sample rows was not exercised in our test run.
Done when stale_flags.csv exists with one row per checked moment and the user has approved the sample rows.
Compliance
- Index only training recordings the organization owns or has the rights to upload to a third party service. Courses licensed from a vendor may not allow it; confirm before Step 4.
- People on camera: trainers and any learners visible in a live class recording were recorded for training. Ask before Step 4 whether any recording includes learners who did not agree to that, and leave it out if so. If a course was made for minors (a high school internship program, for example), do not index it without guardian consent.
- The skill flags where old guidance appears. It does not judge whether a moment is non compliant and does not evaluate the trainer; the content owner decides.
- 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. It does no face recognition and identifies no one.
- Nothing is posted or sent. stale_flags.csv stays in the working folder until the user shares it.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a slide window starts on the title card or runs into the next slide, which can show the new rule; in our test run the median slide window was 16 seconds | Vivu returns a time range that contains the slide, not the slide's exact start and end | take start, end and slide text from the contact sheet and the full frame, never from the window edges |
| (observed) the spoken search misses a sentence that states the old rule in another shape; the first wording in our test run returned 1 real and 1 missed | the query described expenses needing approval, the trainer talked about approving a report above the limit | name the number in the query, cover both nouns (expense or report), and grep the transcripts for the number in digits and words |
| (observed) the summary segments around a spoken hit describe the slide and say nothing about the spoken rule | segment titles follow what is on screen, such as "Pre-submission checklist" | confirm every spoken hit from the transcript lines inside the window |
| (observed) the reason text quotes a spoken rule with the number in digits, as if it were written | the reason is a paraphrase, not a transcript | copy said_local from the transcript, never from the reason |
| a slide hit's reason names the old number, but the frame shows the new number or a nearby amount | screen text reasons can describe text that is not on screen | read the number on the full frame and reject the hit if it is not the old one |
| an old system screen is not found when its name is not visible | the UI search was only tested with the old tool's name in the title bar | describe the old screen's layout in the query and look through contact sheets of the demo sections by eye |
| a list stops at exactly maximum_results | the cap cut the list short | raise maximum_results and rerun that query |
| a shortlisted course has no flag | an empty result does not prove the course never mentions the change | tell the user and offer a slide contact sheet of that course for a look by eye |
| "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 courses.csv | Vivu replaced spaces or punctuation in the name | compare names with those characters replaced by "_" |
| ffmpeg stops with "Error opening output files: No such file or directory" | the frames folder does not exist yet | run the mkdir -p command from Step 1 and run later commands from inside stale-review/ |