From 07fd6520be025c0e6a7586e1e3e6d77835c116bc Mon Sep 17 00:00:00 2001 From: Saikumar Chintada Date: Fri, 23 Oct 2026 20:29:35 +0530 Subject: [PATCH] Make dcore the single skill: thread-by-thread citizen report - Remove the old dcore summary skill and dcore-report; rename dcore-citizen to dcore - Move fetch.py and transcript_chunks.py into .claude/skills/dcore/scripts/ - Add docs/examples/civic-sense/: report and provenance for the WTF Discourse civic-sense debate - Tests check both templates and the example against the report schema - README describes the one skill Co-Authored-By: Claude Opus 5.5 --- .claude/skills/dcore-citizen/SKILL.md | 110 - .claude/skills/dcore-citizen/template.html | 1744 --------------- .claude/skills/dcore-report/SKILL.md | 119 - .claude/skills/dcore-report/template.html | 1168 ---------- .claude/skills/dcore/SKILL.md | 233 +- .../{dcore-citizen => dcore}/definitions.md | 0 .../{dcore-citizen => dcore}/judgement.md | 0 .../skills/{dcore-citizen => dcore}/schema.md | 0 .../scripts}/transcript_chunks.py | 0 .../template-compact.html | 0 .claude/skills/dcore/template.html | 1962 ++++++++++++++--- .../skills/{dcore-citizen => dcore}/tone.md | 0 README.md | 94 +- .../civic-sense/citizen-provenance.md | 170 ++ docs/examples/civic-sense/citizen.html | 1726 +++++++++++++++ tests/test_dcore.py | 72 +- 16 files changed, 3727 insertions(+), 3671 deletions(-) delete mode 100644 .claude/skills/dcore-citizen/SKILL.md delete mode 100644 .claude/skills/dcore-citizen/template.html delete mode 100644 .claude/skills/dcore-report/SKILL.md delete mode 100644 .claude/skills/dcore-report/template.html rename .claude/skills/{dcore-citizen => dcore}/definitions.md (100%) rename .claude/skills/{dcore-citizen => dcore}/judgement.md (100%) rename .claude/skills/{dcore-citizen => dcore}/schema.md (100%) rename .claude/skills/{dcore-report => dcore/scripts}/transcript_chunks.py (100%) rename .claude/skills/{dcore-citizen => dcore}/template-compact.html (100%) rename .claude/skills/{dcore-citizen => dcore}/tone.md (100%) create mode 100644 docs/examples/civic-sense/citizen-provenance.md create mode 100644 docs/examples/civic-sense/citizen.html diff --git a/.claude/skills/dcore-citizen/SKILL.md b/.claude/skills/dcore-citizen/SKILL.md deleted file mode 100644 index c757066..0000000 --- a/.claude/skills/dcore-citizen/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: dcore-citizen -description: Turn a debate, panel, townhall or deliberation transcript (or a YouTube link) into a one-minute citizen report that follows each thread of the conversation - what progress was made, what was missed because of distractions and what that cost, where the time went by distraction type, who pulled each thread off course (with timestamps), and a simulated replay of one fair path per thread. Use when asked for a citizen report, distraction analysis, thread analysis or missed-opportunity report on a discussion. ---- - -# dcore-citizen - -A conversation is several threads running side by side, like rivers. Each -thread either reaches somewhere (agreement, or a clearer disagreement) or -gets pulled off course by distractions. This report follows each thread and -shows a busy citizen, in about a minute: - -- **Progress.** The good: what was reached. The bad: what was within reach - and missed, which distraction cost it, and what that leads to. -- **Distractions.** How much of the time went to them, by label; each thread - with its distractions laid over it; who made each one, with the timestamp. -- **Replay.** For a thread that got stuck or dropped, one fair way it could - have gone with the same voices and views. - -Think of a chess engine's game review: it doesn't tell you which side to -play. It shows the better move you missed and what it cost. - -## Files - -The steps below stay fixed. The parts you tune live in their own files; read -all four before starting: - -- `definitions.md`: what a thread, a bid, a distraction and progress are, and - the plain labels the page uses. -- `judgement.md`: how each call is made (dodges, missed steps, replays, time). -- `tone.md`: principles, wording, and what makes the page effortless to read. -- `schema.md`: the JSON the page is built from, and its limits. - -If one of these files contradicts this one, the specific file wins for its -subject. - -## Input - -A transcript, ideally with timestamps (YouTube "Show transcript" text, -.vtt/.srt, or pasted text). For a YouTube link, run -`python .claude/skills/dcore/scripts/fetch.py --out debates/` (or -`--from pasted.txt`) and read `debates//transcript.txt`; if YouTube is -blocked, ask for the pasted transcript. If there are no timestamps, estimate -from position in the text and say timings are approximate. Work from the -transcript in its own language; write the report in the language the user asks -for (default English). - -## Steps - -1. **Read the whole transcript.** Note the central question, the voices (by - role), and what triggered the conversation, in one or two sentences. -2. **Split it into threads.** Name 3 to 6 threads by their questions. Link - returns to a thread across gaps. -3. **Mark the moves.** Walk through each thread in time order. Mark every bid - (question or point) and whether it was engaged with, every good move, and - every distraction with its kind and label (`definitions.md`), who made it, - and which thread it pulled away from. List every weighty bid that got no - reply at all, with its owner, and every answer that left a gap, with the - follow-up that would have closed it (`judgement.md`). -4. **Decide where each thread ended** (`converged`, `clarified`, `bounded`, - `stuck`, `dropped`), and write one line on where it got to and one on - where it was pulled off course. -5. **Find the missed steps.** At each distraction and each bid nobody took - up, ask what progress was one or two moves away using only what had been - said so far (`judgement.md`). Keep the 2 to 4 that matter most, each with - the moment it was lost. -6. **Write the consequences** for every reached and missed step, and the - shared outcome if misses lead to the same end. -7. **Replay** each `stuck` or `dropped` thread from the moment it went wrong: - 3 to 6 lines by role, ending in the state the replay reaches. -8. **Slice the time.** Cover the whole run time with segments (`fwd`, `talk`, - `side`, `idle`), each carrying its thread, and a label for `side`. -9. **Fill the voices.** Good moves as green dots; omissions as red dots with - the better move and its cost. Distractions become red dots, and unanswered - bids orange dots and gaps hollow orange dots, automatically from their - lists. -10. **Name the biggest hidden loss** as a titled insight with one concrete - example (usually a thread dropped without any shouting). -11. **Write the "next time you watch, ask" questions** that teach the reader to - spot what this report found. Default: - - To spot a distraction: Was the question answered, or did the talk move - to something else? / Did the answer cover what was asked, or only part of - it, or a promise? / When someone made a strong claim, did they back it - up, or just repeat it louder? / Did they argue the point, or attack and - mock the other side? / Did the host ask questions, or argue for one - side? - - To spot talk that went nowhere: When someone made a point, did anyone - reply to it? / When a question was dropped, did anyone bring it back? / - At the end, did anyone say what was settled and what is still open? -12. **Link every timestamp to its source.** Put the video link in `youtube`. - Without one, put the raw transcript in `transcript` as `[start_sec, text]` - chunks (`python3 .claude/skills/dcore-report/transcript_chunks.py - debates//transcript.txt` prints them for `[m:ss] text` lines). -13. **Build the page** as `schema.md` says, then run the checks in `tone.md`. -14. **Write `debates//citizen-provenance.md`**: each thread with every - marked move and its time; every segment with a one-line reason; every - distraction with the rule it met; every bid marked Engaged / Dodged / - Nobody took it up; and the reasoning behind each missed step. - -## Output - -`debates//citizen.html` and `debates//citizen-provenance.md`. Keep -the page local: don't publish it as an artifact or share it unless the user -asks. Open it in the browser with `open debates//citizen.html`. - -## Reply to the user - -Give the file path and three lines: what was reached, the biggest miss and what -cost it, and the share of time lost to distractions. Mention that the replays -are one fair path, not what happened, and where the provenance file is. diff --git a/.claude/skills/dcore-citizen/template.html b/.claude/skills/dcore-citizen/template.html deleted file mode 100644 index 6f75342..0000000 --- a/.claude/skills/dcore-citizen/template.html +++ /dev/null @@ -1,1744 +0,0 @@ -Citizen Report - - - - -
-
-
-

-

-
- -
-

What got done

-

-
-
- -
-

Distraction analysis

-

What pulled the debate off course.

-
-
Progress: agreed, answered or made clearer
-
On topic, but left open
-
Distractions
-

-
-

-
Points left unanswered
-

-
Answers that left a gap
-
Minute by minute -
-
-
-
- Progress - Left open - Distractions - Intro and breaks -
-
-
- -

Where the distraction time went

-
-
Who did what, and when
- -

-
- -
-

Thread by thread

-

Each bar is one thread across the whole run time. Red is where a distraction pulled it off course. Tap a thread for what happened and one fair way it could have gone.

-
-
-
- -
-

Accountability

-

Who moved the debate forward, and who held it back.

-
-
- -
-

Next time you watch a debate, ask

-

To spot a distraction

-
    -

    To spot talk that went nowhere

    -
      -
      - - - -
      -
      - - - - diff --git a/.claude/skills/dcore-report/SKILL.md b/.claude/skills/dcore-report/SKILL.md deleted file mode 100644 index 7bcbc1e..0000000 --- a/.claude/skills/dcore-report/SKILL.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -name: dcore-report -description: Turn a debate, discussion, panel or deliberation transcript (or a YouTube link) into a one-screen "60-second report card" for busy citizens - progress reached vs missed, where the time went, and how each voice did, with tap-to-expand detail. Use when asked for a report card, scorecard or accountability report on a discussion. ---- - -# dcore-report (60-second report card) - -## Why this exists - -Debates, discussions and deliberations are all conversations. Most end in shouting and slogans that people consume without thinking. This report shows a community what a conversation actually achieved, what it could have achieved if everyone had argued in good faith (keeping their own views), and who moved it forward or held it back. - -The goal is a habit, not a document: people who are busy, with little time **and little attention**, should learn to ask "was the question answered, did anyone reply, did anyone agree?" instead of picking up the slogan. The report card also holds participants and the moderator accountable. It must never discourage disagreement, only irrational moves. - -Think of a chess engine's game review: it doesn't tell you which side to play, it shows the better move you missed and what it cost. - -## Principles (never break these) - -- **Take no sides.** Grade moves, never positions. Anyone may argue for or against. Criticism should land on every side where it is earned, the moderator included. -- **Actions, not labels.** Describe what was done ("changed the subject"), never who someone is ("liar"). Name voices by role (Host, Ruling party, Opposition, Analyst, Former official, Resident 1...), not personal names. -- **The ideal is simulated.** The "could have" path is one plausible better path, not the truth. Say so in the footer. -- **Be accurate; don't overgeneralise.** "More distrust in elections", not "people stop trusting elections". Numbers quoted by speakers are unchecked unless you checked them; say so. -- **Be specific everywhere.** Every line must stand alone, because people skim and do not read cohesively. No "it", "they", "this" without the noun. Name the institution, the question, the thing. -- **Provenance for every judgement.** Each step, each dot, each time segment and each distraction must trace back to a timestamped moment in the real conversation. The page shows these behind toggles; `provenance.md` holds the rest. -- **Plain words only.** No jargon, and no fancy debate vocabulary either: say "Changed the subject", not "Red herring"; "Attacked motives, not the argument", not "Ad hominem". - -## How this differs from `dcore` - -`dcore` explains a discussion to someone who missed it and never judges people. `dcore-report` deliberately holds participants and the moderator accountable: it grades **moves** against a visible good-faith checklist and compares what happened with a simulated good-faith version. It still never labels a person or grades a position. - -## Input - -A transcript, ideally with timestamps (YouTube "Show transcript" text, .vtt/.srt, or pasted text). For a YouTube link, run `python .claude/skills/dcore/scripts/fetch.py --out debates/` (or `--from pasted.txt`) and read `debates//transcript.txt`; if YouTube is blocked, ask for the pasted transcript. If there are no timestamps, estimate from position in the text and say timings are approximate. If the transcript is in Hindi or another language, work from it directly and write the report in the language the user asks for (default English). - -## Process - -1. **Read the whole transcript.** Identify the central question, the voices (by role), and the context (what triggered the debate) in one or two sentences. -2. **Find what was settled.** Progress means only: a point all sides **agreed**, or a question that was **answered** (and the answer accepted or not challenged). On-topic talk that nobody engaged with is NOT progress. -3. **Simulate the good-faith version.** Same voices, same positions, but each one: answers the question asked, backs up a claim or says it's unchecked, doesn't repeat, gives ground when a point lands, argues the issue not motives or record, says what would change their mind; the moderator keeps their own view out and sums up. From this, list 2-4 **steps within reach** (agreements a fair panel could plausibly reach: a shared list of questions, a shared test, a condition, a next action). Mark each step reached or missed. -4. **Write consequences for each step.** For reached steps: "What this leads to" plus 1-2 lines. For missed steps: "Because " followed by 2-3 lines that escalate ("So ... So ..."). Harm compounds non-linearly, so each line is a step worse. If two or more misses lead to the same end, write one shared **outcome** (accurate, not overgeneralised). -5. **Back each step with the real moments.** For every step, list the timestamped moments from the actual conversation that show it was reached, or how close the panel came and where it let go (role + what was said). Moments without a time are things nobody said ("The Opposition never said what would make it drop the demand"). Optionally add a list the panel could have signed, or a note on another step within reach. These are real, not simulated; they sit behind the step's "What was said on the night" toggle. -6. **Slice the time.** Walk through the transcript and assign every stretch to one of: - - `fwd`: progress (agreed or answered) - - `talk`: on topic, but nobody engaged - - `side`: distraction - - `idle`: intro, network trouble, goodbyes - Record `[start_sec, end_sec, type]` segments covering the whole run time. -7. **List the distractions** two ways: (a) the kinds that occurred, as plain labels with an optional short gloss, no who-did-what, e.g. Changed the subject (instead of answering), Slogans and claims with no source, Attacked motives, not the argument, Mocked instead of answering, Host took a side (instead of moderating), Same point repeated, Moved the goalposts. Only list kinds that occurred. (b) Each instance with time, who, label, what happened and how long; these sit behind the "Who did what, and when" toggle. -8. **Find the biggest hidden loss.** Usually it is not the shouting but points left unanswered. Write it as a titled insight, naming one concrete example. -9. **Score each voice.** Count notable moves: green = moved it forward (answered, gave a checkable fact, conceded, proposed a fair test/condition, asked a specific question); red = held it back (changed the subject, overstated or unsupported claim, attacked motives or record, mocked, dodged or ignored a question; for the moderator also taking a side, letting detours run, not summing up). One dot = one timestamped move. For each red dot write the **better move** (keeping the same view) and its **cost**. Write a one-line remark per voice ("pushed for evidence, but took a side"), short enough to fit one line. -10. **Write the "next time you watch a debate, ask" questions** in two groups that teach people to spot what this report found. Default: - - To spot a distraction: Was the question answered, or did the talk move to something else? / When someone made a strong claim, did they back it up, or just repeat it louder? / Did they argue the point, or attack and mock the other side? / Did the host ask questions, or argue for one side? - - To spot talk that went nowhere: When someone made a point, did anyone reply to it? / Did anyone agree on anything, even one small fact? / At the end, did anyone say what was settled and what is still open? - Don't ask about citing numbers; people rarely do. -11. **Link every timestamp to its source.** If the conversation is a YouTube video, put its link in `youtube`; every timestamp on the page then opens the video at that second. Otherwise put the raw transcript in `transcript` as `[start_sec, text]` lines grouped into ~20-second chunks (`python3 .claude/skills/dcore-report/transcript_chunks.py debates//transcript.txt` prints them for `[m:ss] text` lines); the page hosts it behind a "Full transcript" toggle and every timestamp opens it at that line. Leave `transcript` empty when there is a YouTube link. The raw transcript is the source as said, so it may contain names; the report itself still uses roles. -12. **Write the provenance file** (`provenance.md`): every segment with a one-line reason, every dot with the rule it met or broke, plus the questions each side faced marked Answered / Dodged (replied but didn't answer) / Ignored, with the reply when there was one. Questions-each-side-faced stays in this file, not on the page. - -## Output - -Copy `template.html` to `debates//report.html` and replace only the JSON inside ` - - diff --git a/.claude/skills/dcore/SKILL.md b/.claude/skills/dcore/SKILL.md index c4e5f38..9d0bac5 100644 --- a/.claude/skills/dcore/SKILL.md +++ b/.claude/skills/dcore/SKILL.md @@ -1,141 +1,110 @@ --- name: dcore -description: Turn a debate, townhall or podcast transcript (or a YouTube link) into one calm HTML page showing what was claimed and how each point fared, a timeline of how the argument moved, and plain answers to a viewer's questions. Use when asked to map, break down, explain or report on a video debate or discussion. +description: Turn a debate, panel, townhall or deliberation transcript (or a YouTube link) into a one-minute citizen report that follows each thread of the conversation - what progress was made, what was missed because of distractions and what that cost, where the time went by distraction type, who pulled each thread off course (with timestamps), and a simulated replay of one fair path per thread. Use when asked to map, break down or report on a debate or discussion, or for a citizen report, distraction analysis, thread analysis or missed-opportunity report on a discussion. --- # dcore -The reader is someone who missed the discussion and wants to know what happened: -what each side argued, what stood up, what fell away, and what was never -answered. You read the transcript and write one JSON object; `template.html` -turns it into the page. Don't write HTML or change the template. - -There is no fact-checking. "Stands" means no one in the discussion knocked a -point down, not that it is true. - -## Principles (non-negotiable) - -- **Anonymous voices.** No names of people in the discussion, even when the - video says them. Refer to a voice by its role in this discussion when that - helps ("the anchor", "the ruling-party side", "the former official", - "a panelist"). People who are talked about are named by office, not by name - ("the CEC", "the opposition leader"). -- **Actions, not labels.** Describe what happened, with a time: "Not - answered; the reply turned to past elections." Never "dodged", "lied", - "propaganda", "biased" or "hypocrite". -- **Take no sides.** No winner, no score, no verdict on a person or a side. - Word every point the way its own speaker would accept. -- **Claims are reported, not endorsed.** "Said 13 crore names were removed", - not "13 crore names were removed". A number that wasn't questioned is still - only a claim. -- **Write in the reader's language.** Use the language of the person asking, - whatever the transcript's language. Translate quotes faithfully. +A conversation is several threads running side by side, like rivers. Each +thread either reaches somewhere (agreement, or a clearer disagreement) or +gets pulled off course by distractions. This report follows each thread and +shows a busy citizen, in about a minute: + +- **Progress.** The good: what was reached. The bad: what was within reach + and missed, which distraction cost it, and what that leads to. +- **Distractions.** How much of the time went to them, by label; each thread + with its distractions laid over it; who made each one, with the timestamp. +- **Replay.** For a thread that got stuck or dropped, one fair way it could + have gone with the same voices and views. + +Think of a chess engine's game review: it doesn't tell you which side to +play. It shows the better move you missed and what it cost. + +## Files + +The steps below stay fixed. The parts you tune live in their own files; read +all four before starting: + +- `definitions.md`: what a thread, a bid, a distraction and progress are, and + the plain labels the page uses. +- `judgement.md`: how each call is made (dodges, missed steps, replays, time). +- `tone.md`: principles, wording, and what makes the page effortless to read. +- `schema.md`: the JSON the page is built from, and its limits. + +If one of these files contradicts this one, the specific file wins for its +subject. + +## Input + +A transcript, ideally with timestamps (YouTube "Show transcript" text, +.vtt/.srt, or pasted text). For a YouTube link, run +`python .claude/skills/dcore/scripts/fetch.py --out debates/` (or +`--from pasted.txt`) and read `debates//transcript.txt`; if YouTube is +blocked, ask for the pasted transcript. If there are no timestamps, estimate +from position in the text and say timings are approximate. Work from the +transcript in its own language; write the report in the language the user asks +for (default English). ## Steps -1. **Get the transcript.** Use pasted text or a file if you were given one. - For a YouTube link, run - `python .claude/skills/dcore/scripts/fetch.py --out debates/` and - read `debates//transcript.txt`. If YouTube is blocked (common from - cloud machines), ask for YouTube's "... > Show transcript" text or a - `.vtt`/`.srt` file, then rerun with `--from --lang `. `` - is a short kebab-case slug of the topic. - -2. **Read it all** before writing anything. Captions are often split into - 2-3 word lines; read them as running speech. Work out the voices from - context: who asks and hands over the floor, who argues which line, who - is addressed. Voices stay internal; the page never shows a voice list. - -3. **Find the question and the options.** State the question the discussion - was really about, as a neutral question (`topic`). Then name 2 or 3 - options people argued for, as short neutral phrases ("Resign now", "Wait - for the commission's side"). Use the options the discussion actually - weighed, even if the anchor framed it differently. - -4. **Collect the points.** A point is one distinct claim that pushes for or - against an option. Each distinct claim appears once, however many times - it was repeated; merge repeats into its trail. Put it under the option it - bears on most, as `for` or `against`. Aim for 2 to 5 points per side; - leave out asides, greetings, and process talk. If an option had nothing - raised against it, leave `against` empty. - -5. **Trace each point.** `steps` is the trail that got the point where it - ended, in time order, one short line each, starting with a verb or an - outcome word: "Raised: ...", "Challenged: ...", "Asked: ...", "Answered: - ...", "Not answered; ...", "Repeated ...", "Accepted by ...". Use the - caption's start time (`m:ss` or `h:mm:ss`). Keep 1 to 6 steps; keep only - steps that changed how the point stood. - -6. **Decide where each point ended** (`end`), from the discussion alone: - - `holds` (shown "stands"): another voice accepted or repeated it, or - every challenge to it was answered and not pressed again. - - `challenged` (shown "disputed"): a challenge was left standing without - a reply, or the reply was itself disputed. - - `fails` (shown "fell away"): its own side gave it up, or a challenge - was accepted. - - `open` (shown "never taken up"): no other voice engaged with it at - all, or it was a question that was never answered. - - Then write `outcome`: the end state in a word or two, then the reason in - one short sentence ("Disputed. No reply came."). - -7. **Write each option's `lean`**: one neutral sentence on where the option - stands as a whole ("Its main support fell away; the cost to families still - stands."). Never say which option won. - -8. **Pick the moments** for the timeline: 3 to 6 points where the argument - changed direction, not every exchange. Each gets a short title, 1 to 3 - sentences, and a `shift` line saying what changed ("Falls away: ...", - "Left disputed: ...", "Ends open: ..."). Mark at most one moment - `"kind": "turn"` (the turning point) and at most one `"common"` (where - people agreed); leave the rest `""`. - -9. **Answer the viewer's questions.** Write 4 to 6 questions a viewer would - ask, in their words, each answered in one or two sentences from the - discussion alone, with an `answer` tag: `settled`, `leans yes`, - `leans no`, `disputed` or `open`. Include at least one question the - discussion left open. - -10. **Build the page.** Copy `template.html` to `debates//index.html` and - replace only the JSON inside ` + diff --git a/.claude/skills/dcore-citizen/tone.md b/.claude/skills/dcore/tone.md similarity index 100% rename from .claude/skills/dcore-citizen/tone.md rename to .claude/skills/dcore/tone.md diff --git a/README.md b/README.md index 4fbe040..c063f98 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,71 @@ # dcore -The [`dcore`](.claude/skills/dcore/SKILL.md) skill turns a debate, townhall or -podcast transcript (or a YouTube link) into one calm HTML page for someone who -missed it: +The [`dcore`](.claude/skills/dcore/SKILL.md) skill turns a +debate, panel, townhall or deliberation transcript (or a YouTube link) into a +one-minute report for a busy citizen. It follows each thread of the +conversation, like a chess engine's game review: it doesn't say which side to +take, it shows the better move that was missed and what it cost. -- **How each point fared:** for each option argued, the points for and against - it, each with a short timestamped trail and where it ended (stands, disputed, - fell away, never taken up). -- **Timeline:** the few moments where the argument changed direction. -- **Your questions, answered:** plain answers to what a viewer would ask. +- **Progress:** what each thread reached (agreed, clearer, agreed to differ) + and what was one or two moves away but missed, with when it slipped, how, + and what that leads to. +- **Distractions:** how much of the run time went to them, by plain label + ("changed the subject", "host took a side"), who made each one, with + timestamps. +- **Left open:** points nobody replied to, and answers that left a gap. +- **Replay:** for each stuck or dropped thread, one fair way it could have + gone with the same voices and views. +- **Accountability:** each voice's good moves and misses as dots, never a + score, each miss with the better move and its cost. -Claude reads the transcript and writes one JSON object into a copy of -[`template.html`](.claude/skills/dcore/template.html); there's no other build -step. `scripts/fetch.py` is optional plumbing that gets YouTube captions or -reads a pasted transcript. It doesn't fact-check. +It grades moves, never positions. Every timestamp links to the video at that +second, and every judgement traces to a moment in a provenance file. -```bash -python .claude/skills/dcore/scripts/fetch.py --out debates/ # or --from pasted.txt -# Claude writes debates//index.html from the template (see the skill) +**Example:** [`docs/examples/civic-sense/`](docs/examples/civic-sense/) is the +report on a WTF Discourse studio debate, "Indian cities are broken because +Indians lack civic sense" ([video](https://www.youtube.com/watch?v=rBPLwZI5whY)): +the [page](docs/examples/civic-sense/citizen.html) and its +[provenance](docs/examples/civic-sense/citizen-provenance.md). Open the page in +a browser. + +## Run it + +In Claude Code, from this repo: + +``` +/dcore https://www.youtube.com/watch?v= ``` -## dcore-report +Claude fetches the captions, writes `debates//citizen.html` and +`debates//citizen-provenance.md`, and opens the page. Add "upload to +artifact" to also publish it as a private claude.ai artifact. -The [`dcore-report`](.claude/skills/dcore-report/SKILL.md) skill turns the same -kind of transcript into a one-screen report card for busy citizens: +To fetch captions yourself (needs `youtube-transcript-api` 1.x): -- **How far they got:** the steps a good-faith panel could have reached, each - marked reached or missed, with what each one leads to. -- **Where the time went:** progress, on-topic talk nobody engaged with, and - distractions, in plain words ("changed the subject", "host took a side"). -- **Report card:** each voice's moves that moved the debate forward or held it - back. -- **Next time you watch, ask:** questions for spotting distractions and talk - that goes nowhere. +```bash +uv run --python 3.12 --with "youtube-transcript-api>=1.0" \ + python .claude/skills/dcore/scripts/fetch.py --out debates/ +# or, with a pasted transcript or .vtt/.srt: +python .claude/skills/dcore/scripts/fetch.py --out debates/ --from pasted.txt +``` + +## How it's built + +Claude reads the transcript and writes one JSON object into a copy of +[`template-compact.html`](.claude/skills/dcore/template-compact.html) +([`template.html`](.claude/skills/dcore/template.html) is the fuller +variant); there's no other build step. The parts you tune live beside +`SKILL.md`: -Collapsed, the page reads in about a minute. Toggles open the detail in place: -the real timestamped moments behind each step, who did what and when, the -minute-by-minute timeline, and every move behind a voice's dots with the better -move and its cost. +| File | Holds | +| --- | --- | +| `definitions.md` | What a thread, bid, distraction and progress are; the page's labels | +| `judgement.md` | How each call is made: dodges, gaps, missed steps, replays, time | +| `tone.md` | Principles, wording, and the pre-publish checks | +| `schema.md` | The JSON the page is built from, and its limits | -Unlike `dcore`, it grades moves (never positions) to hold participants and the -moderator accountable. The "could have" part is a simulated good-faith version, -labelled as such. Claude fills the JSON in -[`template.html`](.claude/skills/dcore-report/template.html), as with `dcore`. +`scripts/transcript_chunks.py` turns `[m:ss] text` lines into transcript +chunks for pages without a video link. Nothing is fact-checked: numbers are +reported as speakers said them. -Tests: `pip install pytest && pytest -q tests`. +Tests: `uv run --with pytest pytest -q tests`. diff --git a/docs/examples/civic-sense/citizen-provenance.md b/docs/examples/civic-sense/citizen-provenance.md new file mode 100644 index 0000000..6bf146f --- /dev/null +++ b/docs/examples/civic-sense/citizen-provenance.md @@ -0,0 +1,170 @@ +# Provenance: WTF Discourse, "Indian cities are broken because Indians lack civic sense" (rBPLwZI5whY) + +Source: https://www.youtube.com/watch?v=rBPLwZI5whY (48:54). Timings and quotes from YouTube auto-captions (English). The captions carry no speaker labels; speakers of short interjections are inferred from context (e.g. 15:10 Kolkata line = Host, because the For speaker follows with "I can give you one more"; 22:48 "over-enforced" = For the motion; 22:46 "lacking enforcement" = Host). + +**Voices (by role):** Host (comedian host who also moderates); For the motion (journalist, YouTuber, former Delhi candidate); Against the motion (urban researcher, KRVIA faculty); Audience members 1–3 (AMA). + +**Central question:** Are Indian cities broken because Indians lack civic sense? **Trigger:** episode of a sponsored studio debate series. Studio poll: 27% for / 73% against at 5:33; 29% / 71% at 46:31. + +## Threads and moves + +### define: What does "civic sense" mean? → converged +- 12:16 For defines: social awareness + ethics + responsibility to shared commons, internal moral compass → **defined a word both use** (fwd). +- 13:04 Against: "What do you mean by civic sense? ... vague concept" (bid, answered by the definition just given). 13:24: "the way you define it ... Well, I agree with you" → **agreed in part** (fwd). +- 19:05 For asks Against to define. 19:09 Against: "No, I don't because I don't understand the concept" → reply came after Host's prompt at 19:15 (fair window), so not a dodge. +- 19:18 Against: respect for formal and informal rules; Milan bicycle anecdote → **answered the question** (fwd). +- 20:15 Against: "doesn't come because you're born with it ... socialized into it". +- 21:08 For: "you said ... socialized into it, which means our society is not socializing people" → **agreed** (fwd; convergence on "learned, not inborn"). +- 28:35 Host: civic sense is "also the good that you don't [do]" — talk. + +### cause: Do people shape institutions, or institutions shape people? → clarified +- 6:50 For opening: Ambedkar, "no political change ... without social change"; "society is broken" (talk, bid to the panel; engaged by Against at 16:52). +- 9:20 Against opening: sweeping claims are untrue; Mumbai buses declined after privatization (10:17, checkable example); colonial sewers and plague blame (11:01). +- 10:33 Against: "didn't stop working because there was some genetic mutation in Indians" → **distraction: Twisted the other side's point** (For had not claimed inborn traits). +- 13:39 Against: context and institutions decide behaviour; design for normal people (fwd as part of rebuttal segment). +- 14:14 Host: good behaviour without good infrastructure? 14:33 Against: yes, floods/COVID; need institutions that reward it. 14:56 Host: only in crises? — engaged. +- 15:10 Host: Kolkata bridges corroding from paan, "This is actual ... Fact" → **distraction: Host took a side** (asserted evidence for one side instead of asking). +- 15:27 For: Tejas train vandalised on day one; ownership lens (talk). +- 16:21 For: municipal staff also lack civic sense (bid) → 16:38 Against: problem is power and incentives → engaged. +- 16:52 Against: Ambedkar meant institutions; caste is an institution. +- 17:47 Host to Against: BMW owner throwing trash, how do you justify? → For answers (18:07) with "paisa fek tamasha dekh" and caste. +- 18:41 For: "I completely agree ... caste is an institution [that] needs to be reformed" → **gave ground** (fwd). +- 21:25 For: school-cleaning rule (Japan/Korea) would fail here because of caste (bid). +- 22:07 Against: Japan's waste decentralization; behaviour followed policy → **gap: Answered part of it** (caste obstacle not addressed). matters: true. +- 24:02 Host: Indore — government first or citizens first? 24:13 Against: "I don't know much about Indore" (an answer), then Mumbai: movements exist, planning law good, power lies with builders → engaged (talk). +- 25:32 Host: Indians follow rules abroad — fear or imitation? Against: imitation; institutions shape behaviour (talk). +- 26:07 Host: not inherently uncivic, just need better incentives? → **narrowed the question** (Host forward dot). For: both levels; schooling. Against: "best education ... is a good system". For: YouTube quip, then 26:57 schools should teach questioning authority (engaged within window). +- 31:47 Against hot take: parties work at staying in power, not services (talk; no reply). +- 34:54–36:32 For pressure test: chooses better citizens, same city, with reasons → **said where they differ** (fwd). +- 37:48–38:31 Against pressure test: rejects both options with a reasoned third (redesign with vendors, Street Vendors Act) → engaged; **said where they differ** (fwd). +- 38:36 Host: "it's the right answer, we will allow you. This is the democracy we need" → **distraction: Host took a side**. +- 44:54 Against closing: civic rights, not civic sense; institutions (talk). +- Ended `clarified`: shared ground (learned behaviour, caste as institution) and one stated split (people first vs design first). + +### enforcement: Do penalties and enforcement work? → dropped +- 22:46 Host: "We are lacking enforcement, then." (bid) +- 22:48 For: "we are over-enforced"; people break rules because they can bribe (engaged). +- 23:09 Against: disagrees on enforcement; BMC fined contractor 7 lakh after tree fell on school bus; "I'll just pay the fine" vs ~50 lakh compliance → **gave a checkable fact**; bid → **Nobody took it up** (Host moved to Indore at 24:02). Owner: Host (aimed at the panel / at the enforcement framing). matters: true. +- 41:35 Audience 2: ticket checker ignores groundnut littering — "where is the blame?" → **Nobody took it up** (Audience 3 next). Owner: Host. matters: false. +- Ended `dropped`. + +### duty: What must citizens do when the city fails them? → converged +- 27:23 Host: broken window — whose job to fix the first one? (bid) +- 27:43 For: citizen should ask the right person, keep asking; 27:55 "Agitate." → **answered the question** (fwd). +- 27:56 Against: critiques broken-windows theory, then: direct action helps but institutions remain responsible (engaged, talk). +- 29:43 Host hot take: why no ownership of the city? (talk) +- 42:20 Audience 3: why should citizens do the job given to government? (bid) +- 42:55 For: "I 100% agree ... not saying do the government's job ... Demand accountability" → **agreed** (fwd). +- 43:16 Host restates; 43:23 For: "I do agree" → **restated fairly, accepted** (fwd). +- 44:41 For closing: organize, educate, agitate. 45:03 Against: "I agree with him that people have to educate, agitate, organize" → **agreed** (fwd). + +### broken: Is the system broken beyond repair? → stuck +- 30:06–31:20 For hot take (stand-up card): bank pens on chains → "They're shitty people" → **distraction: Claimed more than the evidence shows** (generalised to all people from one example). Logged despite the comedy card: the line was later argued with at 41:18. +- 39:14 Audience 1: is it the state's job to raise standards of living? 39:43 For: citizens don't realise their power; empathy for those below; "institutions are garbage" → **gap: Answered part of it** (state duty not addressed). matters: false. +- 40:27 Against: "my friend thinks ... there is no saving the current situation"; 40:51 For: "100% broken" → **restated the other side fairly (accepted)** (fwd). +- 40:52–41:33 Against: local trains work even in the rains; "understand what works and why" → **Nobody took it up** (Audience 2 next; For never returns to it). Owner: For the motion. matters: true. +- 44:19 For closing: "politics is dead, which means institutions are dead" → **distraction: Same point repeated** (restated after an unanswered counterexample). +- Ended `stuck`. + +## Every bid: Engaged / Dodged / Nobody took it up +| Time | By → to | Bid | Result | +|---|---|---|---| +| 6:50 | For → panel | Society, not infrastructure, is broken | Engaged (16:52) | +| 13:04 | Against → For | What do you mean by civic sense? | Engaged (already defined; agreed in part) | +| 14:14 | Host → Against | Good behaviour without infrastructure? | Engaged | +| 16:21 | For → Against | Municipal staff lack civic sense too | Engaged | +| 17:47 | Host → Against | BMW owner littering, how to justify? | Engaged (by For) | +| 19:05 | For → Against | Define civic sense | Engaged after Host prompt | +| 21:25 | For → Against | Caste would block school cleaning | Engaged, gap (Answered part of it) | +| 22:46 | Host → panel | Lacking enforcement? | Engaged | +| 23:09 | Against → panel | 7 lakh fine cheaper than compliance | Nobody took it up | +| 24:02 | Host → Against | Indore: government or citizens first? | Engaged | +| 25:32 | Host → panel | Abroad: fear or imitation? | Engaged | +| 26:07 | Host → panel | Not uncivic, need incentives? | Engaged | +| 27:23 | Host → panel | Whose job to fix the first window? | Engaged | +| 31:47 | Against → (hot take) | Parties work at staying in power | Nobody took it up (not weighty enough to list) | +| 39:14 | Audience 1 → For | State's job to raise living standards? | Engaged, gap (Answered part of it) | +| 40:52 | Against → For | Local trains work; ask what works and why | Nobody took it up | +| 41:35 | Audience 2 → panel | Ticket checker ignores littering | Nobody took it up | +| 42:20 | Audience 3 → For | Why do the job we gave the government? | Engaged → agreement | + +No dodges (no reply that talked past a bid without later engagement). + +## Distractions and the rule each met +- 10:33 Against, Twisted the other side's point: attributed an inborn-trait claim ("genetic mutation") that the For side had not made. +- 15:10 Host, Host took a side: stated evidence for the motion as "fact" rather than putting it to both speakers. +- 30:53 For, Claimed more than the evidence shows: verdict on all people from one example (bank pens); took over the hot-take slot. +- 38:36 Host, Host took a side: declared one side's answer "the right answer". +- 44:19 For, Same point repeated: restated "institutions are dead" after a counterexample he had not answered. +- Considered and not logged: Host's opening monologue (frames both sides); hot-take and pressure-test formats (show structure, counted idle where setup); 26:50 YouTube quip (engaged within the next turn); 27:56 Against's critique of broken-windows theory (answered the question right after). + +## Segments (one-line reasons) +| Range (s) | Type | Thread | Reason | +|---|---|---|---| +| 0–410 | idle | | Intro, sponsor bits, speaker intros, opening poll, rules | +| 410–540 | talk | cause | For opening: society is broken; no engagement yet | +| 540–560 | idle | | Time-limit banter | +| 560–633 | talk | cause | Against opening: sweeping claim; Mumbai buses | +| 633–640 | side | cause | "Genetic mutation" framing | +| 640–685 | talk | cause | Privatization; colonial sewers and plague | +| 685–707 | idle | | Host transition to rebuttals | +| 707–772 | fwd | define | For defines civic sense | +| 772–779 | idle | | Host aside | +| 779–843 | fwd | define | Against agrees in part with the definition; institutions for normal people | +| 843–854 | idle | | Applause, format | +| 854–910 | talk | cause | Infrastructure vs behaviour Q&A | +| 910–927 | side | cause | Host asserts Kolkata paan "fact" | +| 927–1012 | talk | cause | Tejas train, ownership, municipal staff, power and incentives | +| 1012–1121 | talk | cause | Ambedkar and caste; BMW question; privileged litter | +| 1121–1140 | fwd | cause | For agrees caste is an institution to reform | +| 1140–1158 | talk | define | For asks for definition; Against has none yet | +| 1158–1215 | fwd | define | Against defines civic sense | +| 1215–1268 | talk | define | Socialized, policy cascade | +| 1268–1279 | fwd | define | For builds on "socialized" | +| 1279–1327 | talk | cause | Socializing; caste blocks school cleaning | +| 1327–1366 | talk | cause | Japan waste answer (gap) | +| 1366–1442 | talk | enforcement | Enforcement, bribes, 7 lakh fine (unanswered) | +| 1442–1532 | talk | cause | Indore → Mumbai, builders | +| 1532–1620 | talk | cause | Abroad/imitation; incentives; schooling; YouTube quip | +| 1620–1643 | talk | cause | Questioning authority; full circle | +| 1643–1675 | fwd | duty | For answers broken-window question: ask, keep asking, agitate | +| 1675–1715 | talk | duty | Against: direct action, institutions still responsible | +| 1715–1731 | talk | define | Host: also the good you don't do | +| 1731–1783 | idle | | Hot-take format setup | +| 1783–1806 | talk | duty | Hot take question on ownership | +| 1806–1880 | side | broken | Bank pens → "shitty people" | +| 1880–1907 | idle | | Applause, character switch | +| 1907–1980 | talk | cause | Against hot take: parties work at staying in power | +| 1980–2094 | idle | | Pressure-test setup and scenario | +| 2094–2192 | fwd | cause | For chooses better citizens with reasons | +| 2192–2268 | idle | | Scenario setup for Against | +| 2268–2316 | fwd | cause | Against: redesign with vendors | +| 2316–2322 | side | cause | Host: "the right answer" | +| 2322–2354 | idle | | AMA setup | +| 2354–2427 | talk | broken | Audience 1 question; For's partial answer | +| 2427–2464 | fwd | broken | Against restates For fairly; For confirms | +| 2464–2495 | talk | broken | Local trains work (unanswered) | +| 2495–2540 | talk | enforcement | Audience 2: ticket checker ignores littering (unanswered) | +| 2540–2575 | talk | duty | Audience 3 question | +| 2575–2605 | fwd | duty | For agrees; Host restates; For agrees again | +| 2605–2633 | idle | | Transition to closings | +| 2633–2659 | talk | duty | For closing: be better citizens, not just voters | +| 2659–2685 | side | broken | "Politics is dead ... institutions are dead" | +| 2685–2694 | idle | | Host hand-off | +| 2694–2703 | talk | cause | Against: civic rights, not civic sense | +| 2703–2711 | fwd | duty | Against agrees: educate, agitate, organize | +| 2711–2768 | talk | cause | Against: institutions benefit certain groups | +| 2768–2934 | idle | | Final poll, host close, sponsors, outro | + +Time shares: progress 16%, on topic but left open 46%, distractions 4%, intro and breaks 33% (distractions are 6.6% of non-idle time). + +## Missed steps: reasoning +1. **enforcement, lost 24:02.** For (22:48): rules broken because a bribe is cheaper. Against (23:16): contractor pays a 7 lakh fine rather than ~50 lakh compliance. Both describe a penalty costing less than obeying. One move ("are those the same point?") from agreement. No outside facts needed. +2. **broken, lost 41:04.** Examples of good behaviour (clean metro 20:38, railway stations 20:43, Singapore signal 25:32, local trains 40:54) and bad (Tejas train 15:27) were all on the table. Putting one pair side by side is one move and directly tests the motion (same people, different outcomes). +3. **cause, lost 22:07.** For had already agreed caste is an institution (18:41) and raised it as the obstacle to socializing (21:25). Against answered with Japan only. One follow-up ("what would make it work here?") sits exactly where both positions met. + +Shared outcome: all three leave "people or system?" unsettled; the poll moved 27% → 29%. + +## Replays +- enforcement from 24:02: Host joins the two points; both accept "penalties must cost more than obeying"; For keeps "someone must refuse the bribe" open → converged on the mechanism. +- broken from 41:04: For concedes the local train works and raises Tejas; Host asks what differs; both list factors → clarified. diff --git a/docs/examples/civic-sense/citizen.html b/docs/examples/civic-sense/citizen.html new file mode 100644 index 0000000..9871f27 --- /dev/null +++ b/docs/examples/civic-sense/citizen.html @@ -0,0 +1,1726 @@ +Civic Sense Citizen Report + + + + +
      +
      +
      +

      +

      +
      + + + +
      + + + +
      +

      +
      +
      +
      + +
      +

      +
      +
      + +
      +

      What pulled the debate off course.

      +

      +

      +

      +
      +

      Minute by minute

      +
      + + + +
      +
      + + + +
      +
      + +
      +

      Who moved the debate forward, and who held it back.

      +
      +
      + +
      +
      What to ask next time you watch a debate
      +

      To spot a distraction

        +

        To spot talk that went nowhere

          +
          + + + +
          +
          + + + + diff --git a/tests/test_dcore.py b/tests/test_dcore.py index 958d6bb..7eebd4b 100644 --- a/tests/test_dcore.py +++ b/tests/test_dcore.py @@ -1,4 +1,4 @@ -"""Tests for the dcore skill: transcript parsing and the page template's sample data.""" +"""Tests for the dcore skill: transcript parsing and the report pages' data.""" import json import sys @@ -33,67 +33,49 @@ def test_parse_vtt_drops_repeats(): assert [(s["start"], s["text"]) for s in segs] == [(1.0, "hello"), (5.0, "world")] -def sample(): - html = (SKILL / "template.html").read_text(encoding="utf-8") - start = html.index('", start)] - return json.loads(body) - - -def test_template_sample_follows_schema(): - d = sample() - assert d["topic"] and d["about"] and d["options"] and d["moments"] and d["questions"] - for o in d["options"]: - assert o["title"] - for p in o["for"] + o["against"]: - assert p["end"] in {"holds", "challenged", "fails", "open"} - assert p["say"] and p["outcome"] and p["steps"] - assert all(s["t"] and s["did"] for s in p["steps"]) - assert all(m["kind"] in {"", "turn", "common"} for m in d["moments"]) - assert all(q["answer"] in {"settled", "leans yes", "leans no", "disputed", "open"} for q in d["questions"]) - - - -REPORT = Path(__file__).resolve().parents[1] / ".claude/skills/dcore-report" +EXAMPLE = Path(__file__).resolve().parents[1] / "docs/examples/civic-sense/citizen.html" -def report_sample(): - html = (REPORT / "template.html").read_text(encoding="utf-8") +def citizen_report(path): + html = path.read_text(encoding="utf-8") start = html.index('", start)] return json.loads(body) -def test_report_sample_follows_schema(): - d = report_sample() - assert d["question"] and d["context"] and d["steps_caption"] - assert {s["status"] for s in d["steps"]} <= {"reached", "missed"} - assert all(s["text"] and s["because"] and s["chain"] for s in d["steps"]) - for s in d["steps"]: - for block in s.get("detail", []): - assert block.get("items") or block.get("note") - assert all(isinstance(i, str) or i["text"] for i in block.get("items", [])) +@pytest.mark.parametrize("path", [SKILL / "template-compact.html", SKILL / "template.html", EXAMPLE], + ids=["template-compact", "template", "example"]) +def test_citizen_report_follows_schema(path): + d = citizen_report(path) + ids = {t["id"] for t in d["threads"]} + assert d["question"] and d["context"] and 3 <= len(ids) <= 6 + assert {p["status"] for p in d["progress"]} <= {"reached", "missed"} + for p in d["progress"]: + assert p["thread"] in ids and p["text"] and p["chain"] + if p["status"] == "missed": + assert p["lost"]["t"] and p["lost"]["label"] segs = d["segments"] - assert all(t in {"fwd", "talk", "side", "idle"} for _, _, t in segs) + assert all(t in {"fwd", "talk", "side", "idle"} for _, _, t, _, _ in segs) assert segs[0][0] == 0 and segs[-1][1] == d["total_seconds"] assert all(a[1] == b[0] for a, b in zip(segs, segs[1:])), "segments must be contiguous" - assert all(x["label"] for x in d["distractions"]) - labels = {x["label"] for x in d["distractions"]} - assert all(m["t"] and m["who"] and m["what"] and m["label"] in labels for m in d["distraction_moments"]) + assert all(th in ids for _, _, _, th, _ in segs if th) + for t in d["threads"]: + assert t["end"] in {"converged", "clarified", "bounded", "stuck", "dropped"} + assert all(m.get("mark") in {"fwd", "talk", "gap", "side"} for m in t["flow"] if "t" in m) + if t["end"] in {"stuck", "dropped"}: + assert 3 <= len(t["replay"]["lines"]) <= 6 + for x in d["distractions"] + d.get("unanswered", []) + d.get("gaps", []): + assert x["thread"] in ids + assert all(x["label"] and x["who"] and x["better"] and x["cost"] for x in d["distractions"]) assert d["insight"]["title"] and d["insight"]["text"] - for v in d["voices"]: - assert v["name"] and v["note"] and v["forward"] + v["back"] - assert all(m["t"] and m["what"] for m in v["forward"]) - assert all(m["t"] and m["what"] and m["better"] and m["cost"] for m in v["back"]) + assert all(v["name"] and v["note"] for v in d["voices"]) assert d["ask"]["distraction"] and d["ask"]["nowhere"] and d["footer"] assert d["youtube"] or d["transcript"], "timestamps need a source to link to" - starts = [s for s, _ in d["transcript"]] - assert starts == sorted(starts) and all(t for _, t in d["transcript"]) def test_transcript_chunks(tmp_path): import importlib.util - spec = importlib.util.spec_from_file_location("tc", REPORT / "transcript_chunks.py") + spec = importlib.util.spec_from_file_location("tc", SKILL / "scripts/transcript_chunks.py") tc = importlib.util.module_from_spec(spec) spec.loader.exec_module(tc) f = tmp_path / "t.txt"