@thierrynakoa/fire-flow 12.2.1 → 13.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CREDITS.md +25 -0
- package/DOMINION-FLOW-OVERVIEW.md +182 -38
- package/README.md +399 -455
- package/TROUBLESHOOTING.md +264 -264
- package/agents/fire-debugger.md +54 -0
- package/agents/fire-executor.md +1610 -1033
- package/agents/fire-fact-checker.md +1 -1
- package/agents/fire-planner.md +85 -17
- package/agents/fire-project-researcher.md +1 -1
- package/agents/fire-researcher.md +4 -22
- package/agents/{fire-phoenix-analyst.md → fire-resurrection-analyst.md} +394 -394
- package/agents/fire-reviewer.md +552 -499
- package/agents/fire-verifier.md +114 -19
- package/bin/cli.js +18 -101
- package/commands/fire-0-orient.md +2 -2
- package/commands/fire-1a-new.md +50 -15
- package/commands/fire-1c-setup.md +33 -5
- package/commands/fire-1d-discuss.md +87 -1
- package/commands/fire-2-plan.md +556 -527
- package/commands/fire-3-execute.md +2046 -1356
- package/commands/fire-4-verify.md +975 -906
- package/commands/fire-5-handoff.md +46 -5
- package/commands/fire-6-resume.md +2 -31
- package/commands/fire-add-new-skill.md +138 -19
- package/commands/fire-autonomous.md +14 -2
- package/commands/fire-complete-milestone.md +1 -1
- package/commands/fire-cost.md +179 -183
- package/commands/fire-debug.md +1 -6
- package/commands/fire-loop-resume.md +2 -2
- package/commands/fire-loop-stop.md +1 -1
- package/commands/fire-loop.md +2 -15
- package/commands/fire-map-codebase.md +1 -1
- package/commands/fire-migrate-database.md +548 -0
- package/commands/fire-new-milestone.md +1 -1
- package/commands/fire-reflect.md +1 -2
- package/commands/fire-research.md +142 -21
- package/commands/{fire-phoenix.md → fire-resurrect.md} +859 -603
- package/commands/fire-scaffold.md +297 -0
- package/commands/fire-search.md +1 -2
- package/commands/fire-security-scan.md +483 -484
- package/commands/fire-setup.md +359 -0
- package/commands/fire-skill.md +770 -0
- package/commands/fire-skills-diff.md +506 -506
- package/commands/fire-skills-history.md +388 -388
- package/commands/fire-skills-rollback.md +7 -7
- package/commands/fire-skills-sync.md +470 -470
- package/commands/fire-test.md +5 -5
- package/commands/fire-todos.md +1 -1
- package/commands/fire-update.md +5 -5
- package/commands/fire-validate-skills.md +282 -0
- package/commands/fire-vuln-scan.md +492 -493
- package/hooks/run-hook.sh +8 -8
- package/hooks/run-session-end.sh +7 -7
- package/hooks/session-end.sh +90 -90
- package/hooks/session-start.sh +1 -1
- package/package.json +4 -24
- package/plugin.json +7 -7
- package/references/autonomy-levels.md +235 -0
- package/references/behavioral-directives.md +95 -3
- package/references/blocker-tracking.md +1 -1
- package/references/circuit-breaker.md +93 -2
- package/references/context-engineering.md +227 -9
- package/references/honesty-protocols.md +70 -1
- package/references/issue-to-pr-pipeline.md +149 -150
- package/references/metrics-and-trends.md +1 -2
- package/references/research-improvements.md +4 -108
- package/references/sdlc-mapping.md +73 -0
- package/references/state-machine.md +151 -0
- package/skills-library/AVAILABLE_TOOLS_REFERENCE.md +333 -0
- package/skills-library/SKILLS-INDEX.md +57 -558
- package/skills-library/SKILLS_LIBRARY_INDEX.md +532 -0
- package/skills-library/_general/api-patterns/api-field-name-mismatch.md +107 -0
- package/skills-library/_general/api-patterns/streaming-command-timeout.md +122 -0
- package/skills-library/_general/api-patterns/streaming-proxy-cors-bypass.md +102 -0
- package/skills-library/_general/automation/settings-gui-generator.md +172 -0
- package/skills-library/_general/database-solutions/data-type-mapping-reference.md +181 -0
- package/skills-library/_general/database-solutions/mysql-limit-offset-string-coercion.md +102 -0
- package/skills-library/_general/database-solutions/mysql-to-pg-migration.md +195 -0
- package/skills-library/_general/database-solutions/orm-schema-portability.md +193 -0
- package/skills-library/_general/database-solutions/persistent-analysis-storage.md +207 -0
- package/skills-library/_general/database-solutions/pg-to-mysql-schema-migration-methodology.md +190 -0
- package/skills-library/_general/database-solutions/sql-dialect-compatibility-matrix.md +306 -0
- package/skills-library/_general/database-solutions/sqlite-to-pg-migration.md +219 -0
- package/skills-library/_general/frontend/canvas-bubble-animation-grouping.md +270 -0
- package/skills-library/_general/frontend/color-token-migration.md +112 -0
- package/skills-library/_general/frontend/framer-motion-layoutid-grouping.md +150 -0
- package/skills-library/_general/frontend/pyqt6-settings-dialog.md +191 -0
- package/skills-library/_general/frontend/react-flow-animated-layout-switching.md +101 -0
- package/skills-library/_general/frontend/react-hooks-order-debugging.md +141 -0
- package/skills-library/_general/frontend/redux-localstorage-auth-desync.md +126 -0
- package/skills-library/_general/frontend/safari-csp-theme-color-debugging.md +124 -0
- package/skills-library/_general/frontend/safari-sw-cache-poisoning.md +138 -0
- package/skills-library/_general/frontend/svg-sparkline-no-charting-library.md +131 -0
- package/skills-library/_general/growth-marketing/oss-daily-growth-intelligence.md +224 -0
- package/skills-library/_general/integrations/claude-code-local-mcp-integration.md +250 -0
- package/skills-library/_general/integrations/mcp-composite-tool-orchestration.md +200 -0
- package/skills-library/_general/methodology/AGENT_SDK_STANDALONE_TOOLING.md +181 -0
- package/skills-library/_general/methodology/AGENT_TEAMS_GUIDE.md +169 -0
- package/skills-library/_general/methodology/ALAS_STATEFUL_EXECUTION.md +207 -0
- package/skills-library/_general/methodology/AUTO_REVIEWER_SUBAGENT.md +211 -0
- package/skills-library/_general/methodology/CONSISTENCY_CHECK_AMBIGUITY_GATE.md +96 -0
- package/skills-library/_general/methodology/DEAD_ENDS_SHELF.md +4 -4
- package/skills-library/_general/methodology/DISTILL_NOT_DUMP.md +108 -0
- package/skills-library/_general/methodology/EXECUTION_PROGRESS_MONITOR.md +157 -0
- package/skills-library/_general/methodology/HIERARCHICAL_REVIEW_MARS.md +122 -0
- package/skills-library/_general/methodology/MCP_INTER_AGENT_BRIDGE.md +207 -0
- package/skills-library/_general/methodology/MERMAID_WIZARD_DIAGRAMS.md +77 -0
- package/skills-library/_general/methodology/MISSING_DIMENSION_DETECTOR.md +89 -0
- package/skills-library/_general/methodology/MULTI_AGENT_COORDINATION.md +397 -0
- package/skills-library/_general/methodology/OBSERVATION_MASKING.md +100 -0
- package/skills-library/_general/methodology/PHOENIX_REBUILD_METHODOLOGY.md +82 -11
- package/skills-library/_general/methodology/REVIEW_BACKTRACK_PANEL.md +140 -0
- package/skills-library/_general/methodology/REVIEW_FIX_LOOP.md +117 -0
- package/skills-library/_general/methodology/VOTING_VERDICT_ARBITRATION.md +155 -0
- package/skills-library/_general/methodology/ZERO_FRICTION_CLI_SETUP.md +2 -2
- package/skills-library/_general/methodology/dead-code-activation.md +123 -0
- package/skills-library/_general/methodology/debug-swarm-researcher-escape-hatch.md +240 -240
- package/skills-library/_general/methodology/shell-autonomous-loop-fixplan.md +1 -1
- package/skills-library/_general/patterns-standards/GOF_DESIGN_PATTERNS_FOR_AI_AGENTS.md +5 -5
- package/skills-library/_general/patterns-standards/cascading-failure-diagnosis.md +119 -0
- package/skills-library/_general/patterns-standards/domain-specific-layout-algorithms.md +209 -0
- package/skills-library/_general/patterns-standards/python-desktop-app-architecture.md +399 -0
- package/skills-library/_general/patterns-standards/realtime-monitoring-dashboard.md +457 -0
- package/skills-library/_general/patterns-standards/togglable-processing-pipeline.md +169 -0
- package/skills-library/_general/performance/liveclock-extraction.md +112 -0
- package/skills-library/_general/performance/ref-based-canvas-animation.md +117 -0
- package/skills-library/_general/performance/use-visible-interval.md +131 -0
- package/skills-library/_general/testing/playwright-firefox-withcredentials-auth-issue.md +104 -0
- package/skills-library/_quarantine/README.md +30 -0
- package/skills-library/api-patterns/BROADCAST_SCHEDULER_SHARED_EXECUTE_FUNCTION.md +150 -0
- package/skills-library/api-patterns/ERROR_RESPONSE_STANDARDS.md +145 -0
- package/skills-library/api-patterns/EXPRESS_ROUTE_ORDERING_MIDDLEWARE_INTERCEPTION.md +326 -0
- package/skills-library/api-patterns/PAGINATION_PATTERNS.md +137 -0
- package/skills-library/api-patterns/PODCAST_PROGRESS_TRACKING_THREE_ROOT_CAUSES.md +277 -0
- package/skills-library/api-patterns/RATE_LIMITING_TOGGLE.md +155 -0
- package/skills-library/api-patterns/graphql-content-queries.md +708 -0
- package/skills-library/appointment-scheduler-design.md +423 -0
- package/skills-library/automation/AUTO_POPULATE_COMPLETE_GUIDE.md +631 -0
- package/skills-library/automation/CC_WORKFLOW_STUDIO.md +83 -0
- package/skills-library/automation/CLAUDE_CODE_SWARM_MODE.md +95 -0
- package/skills-library/automation/DAEMON_TRIGGER_FILE_IPC.md +195 -0
- package/skills-library/automation/scheduled-content-publishing.md +608 -0
- package/skills-library/awesome-workflows/Blogging-Platform-Instructions/view_commands.md +25 -0
- package/skills-library/awesome-workflows/CREDENTIAL-SECURITY-WORKFLOW.md +109 -0
- package/skills-library/awesome-workflows/DEBUGGING-WORKFLOW.md +124 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/README.md +31 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-principles-example.md +129 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-agent.md +107 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-claude-md-snippet.md +24 -0
- package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-slash-command.md +38 -0
- package/skills-library/awesome-workflows/PARALLEL-RESEARCH-WORKFLOW.md +89 -0
- package/skills-library/awesome-workflows/PHASE-EXECUTION-WORKFLOW.md +97 -0
- package/skills-library/awesome-workflows/SESSION-HANDOFF-WORKFLOW.md +116 -0
- package/skills-library/cms-patterns/content-branch-preview.md +515 -0
- package/skills-library/cms-patterns/inline-visual-editing.md +666 -0
- package/skills-library/cms-patterns/mdx-component-content.md +649 -0
- package/skills-library/cms-patterns/media-manager-abstraction.md +827 -0
- package/skills-library/cms-patterns/schema-driven-form-generator.md +838 -0
- package/skills-library/complexity-metrics/complexity-divider.md +707 -0
- package/skills-library/complexity-metrics/work-with-complexity.md +193 -0
- package/skills-library/creative-multimedia/animation-stack-guide.md +577 -0
- package/skills-library/creative-multimedia/audio-enhancement-pipeline.md +625 -0
- package/skills-library/creative-multimedia/content-repurposing-pipeline.md +1146 -0
- package/skills-library/creative-multimedia/data-visualization-generator.md +862 -0
- package/skills-library/creative-multimedia/doc-to-podcast-pipeline.md +2184 -0
- package/skills-library/creative-multimedia/ffmpeg-command-generator.md +405 -0
- package/skills-library/creative-multimedia/image-optimization-pipeline.md +605 -0
- package/skills-library/creative-multimedia/multi-format-content-generator.md +1759 -0
- package/skills-library/creative-multimedia/og-image-generator.md +635 -0
- package/skills-library/creative-multimedia/podcast-audio-composition.md +1355 -0
- package/skills-library/creative-multimedia/podcast-quality-evaluation.md +1452 -0
- package/skills-library/creative-multimedia/podcast-script-generation.md +1841 -0
- package/skills-library/creative-multimedia/svg-generation.md +750 -0
- package/skills-library/creative-multimedia/text-to-speech-provider-selector.md +1414 -0
- package/skills-library/creative-multimedia/transcription-pipeline-selector.md +677 -0
- package/skills-library/creative-multimedia/video-streaming-setup.md +559 -0
- package/skills-library/database-solutions/AI_RESPONSE_DATABASE_CACHING.md +520 -0
- package/skills-library/database-solutions/CONDITIONAL_SQL_MIGRATION_PATTERN.md +119 -0
- package/skills-library/database-solutions/DATABASE_COLUMN_NAME_MISMATCH.md +393 -0
- package/skills-library/database-solutions/DATABASE_SCHEMA.md +394 -0
- package/skills-library/database-solutions/DATABASE_SCHEMA_VERIFICATION_GUIDE.md +348 -0
- package/skills-library/database-solutions/DATABASE_STRATEGY.md +71 -0
- package/skills-library/database-solutions/ES_MODULE_SEED_SCRIPT_PATTERN.md +52 -0
- package/skills-library/database-solutions/MIGRATION_GUIDE.md +3 -0
- package/skills-library/database-solutions/PLPGSQL_VARIABLE_CONFLICT_FIX.md +208 -0
- package/skills-library/database-solutions/POSTGRESQL_JSONB_DOUBLE_STRINGIFY_FIX.md +245 -0
- package/skills-library/database-solutions/POSTGRESQL_LICENSE_TABLE_DESIGN.md +393 -0
- package/skills-library/database-solutions/POSTGRESQL_UUID_DOCUMENT_RAG_DUAL_SCOPE.md +732 -0
- package/skills-library/database-solutions/POSTGRES_SQL_TEMPLATE_BINDING_ERROR.md +240 -0
- package/skills-library/database-solutions/PRISMA_DB_PUSH_DATA_LOSS_PREVENTION.md +141 -0
- package/skills-library/database-solutions/PRODUCTION_QUERY_OPTIMIZATION_RESTART_FIX.md +389 -0
- package/skills-library/database-solutions/RLS_SECURITY_GUIDE.md +107 -0
- package/skills-library/database-solutions/SCHEMA_ENHANCEMENTS_GUIDE.md +373 -0
- package/skills-library/database-solutions/SCHEMA_MIGRATION_GUIDE.md +368 -0
- package/skills-library/database-solutions/SCHEMA_VERIFICATION_QUICK_REFERENCE.md +104 -0
- package/skills-library/database-solutions/ai-erd-generator.md +1213 -0
- package/skills-library/database-solutions/content-publishing-states.md +631 -0
- package/skills-library/database-solutions/database-schema-designer.md +522 -0
- package/skills-library/database-solutions/er-diagram-components.md +569 -0
- package/skills-library/database-solutions/er-to-ddl-mapping.md +1405 -0
- package/skills-library/database-solutions/erd-creator-textbook-research.md +433 -0
- package/skills-library/database-solutions/erd-react-flow-architecture.md +1965 -0
- package/skills-library/database-solutions/mariadb-aggregate-function-replacement.md +145 -0
- package/skills-library/database-solutions/normalization-validator.md +778 -0
- package/skills-library/database-solutions/postgres-full-text-search-content.md +494 -0
- package/skills-library/database-solutions/postgresql-to-mysql-runtime-translation.md +286 -0
- package/skills-library/database-solutions/regex-alternation-ordering-sql-types.md +92 -0
- package/skills-library/database-solutions/reserved-word-context-aware-quoting.md +142 -0
- package/skills-library/database-solutions/sql-ddl-generator.md +756 -0
- package/skills-library/database-solutions/supabase-connection-pooler-fix.md +102 -0
- package/skills-library/deployment-security/CPANEL_NODE_DEPLOYMENT.md +166 -0
- package/skills-library/deployment-security/DEPLOYMENT.md +275 -0
- package/skills-library/deployment-security/DEPLOYMENT_CHECKLIST.md +363 -0
- package/skills-library/deployment-security/DEPLOYMENT_PLAN.md +669 -0
- package/skills-library/deployment-security/KNEX_DATABASE_ABSTRACTION.md +444 -0
- package/skills-library/deployment-security/LICENSE_KEY_SYSTEM.md +206 -0
- package/skills-library/deployment-security/NODE18_DEPENDENCY_COMPATIBILITY.md +284 -0
- package/skills-library/deployment-security/PHP_INSTALLER_WIZARD_GUIDE.md +315 -0
- package/skills-library/deployment-security/PM2_ENVIRONMENT_VARIABLE_CACHING.md +256 -0
- package/skills-library/deployment-security/PM2_MEMORY_EXHAUSTION_FIX.md +370 -0
- package/skills-library/deployment-security/PRODUCTION_DEPLOYMENT_GUIDE.md +592 -0
- package/skills-library/deployment-security/PRODUCTION_HARDENING_DOCUMENTATION.md +307 -0
- package/skills-library/deployment-security/PRODUCTION_RECOVERY_CHERRY_PICK_PATTERN.md +202 -0
- package/skills-library/deployment-security/PYINSTALLER_CUDA_WHISPER_BUNDLING.md +236 -0
- package/skills-library/deployment-security/SECURITY.md +41 -0
- package/skills-library/deployment-security/SMTP_SSL_HOSTNAME_MISMATCH_SHARED_HOSTING.md +220 -0
- package/skills-library/deployment-security/SPA_SEO_OPTIMIZATION_CPANEL.md +200 -0
- package/skills-library/deployment-security/SUPABASE_EDGE_FUNCTIONS.md +338 -0
- package/skills-library/deployment-security/VERCEL_GITHUB_DEPLOYMENT_GUIDE.md +858 -0
- package/skills-library/deployment-security/VPS_DEPLOYMENT_READINESS.md +356 -0
- package/skills-library/deployment-security/deployment-changes-not-applying.md +241 -0
- package/skills-library/deployment-security/env-file-management-production-local.md +203 -0
- package/skills-library/deployment-security/express-secure-file-downloads.md +413 -0
- package/skills-library/deployment-security/react-production-deployment-desktop-guide.md +2011 -0
- package/skills-library/deployment-security/self-hosted-supabase-coolify-guide.md +1684 -0
- package/skills-library/deployment-security/unique-features-ai-strategy-plaid-security.md +1613 -0
- package/skills-library/deployment-security/vps-deployment.md +135 -0
- package/skills-library/document-processing/WORD_EXPORT_MARKDOWN_FORMATTING.md +482 -0
- package/skills-library/document-processing/document-ai-landingai-integration.md +677 -0
- package/skills-library/document-processing/express-secure-file-downloads-mern.md +413 -0
- package/skills-library/document-processing/express-secure-file-downloads.md +413 -0
- package/skills-library/document-processing/md-to-word-converter.md +318 -0
- package/skills-library/document-processing/pdf-forms-integration/README.md +101 -0
- package/skills-library/document-processing/pdf-forms-integration/SKILL.md +662 -0
- package/skills-library/ecommerce/ADMIN_PRODUCTS_GUIDE.md +428 -0
- package/skills-library/ecommerce/ECOMMERCE_API_REFERENCE.md +776 -0
- package/skills-library/ecommerce/ECOMMERCE_COMPLETION_SUMMARY.md +673 -0
- package/skills-library/ecommerce/ECOMMERCE_IMPLEMENTATION_GUIDE.md +729 -0
- package/skills-library/ecommerce/ECOMMERCE_QUICK_REFERENCE.md +521 -0
- package/skills-library/ecommerce/ECOMMERCE_TESTING_CHECKLIST.md +565 -0
- package/skills-library/ecommerce/ECOMMERCE_WORKFLOW_GUIDE.md +1059 -0
- package/skills-library/ecommerce/PRODUCT_CREATION_EXPANDED.md +522 -0
- package/skills-library/ecommerce/agentic-commerce-protocol.md +203 -0
- package/skills-library/ecommerce/cart-abandonment-recovery.md +236 -0
- package/skills-library/ecommerce/cart-architecture-patterns.md +300 -0
- package/skills-library/ecommerce/cart-item-count-indicator.md +264 -0
- package/skills-library/ecommerce/checkout-ux-conversion.md +227 -0
- package/skills-library/ecommerce/composable-commerce-selection.md +166 -0
- package/skills-library/ecommerce/ecommerce-analytics-patterns.md +167 -0
- package/skills-library/ecommerce/fraud-detection-patterns.md +179 -0
- package/skills-library/ecommerce/inventory-stock-management.md +270 -0
- package/skills-library/ecommerce/order-saga-state-machine.md +336 -0
- package/skills-library/ecommerce/payment-provider-abstraction.md +245 -0
- package/skills-library/ecommerce/pci-compliance-checklist.md +192 -0
- package/skills-library/ecommerce/refund-chargeback-handling.md +177 -0
- package/skills-library/ecommerce/shipping-carrier-integration.md +218 -0
- package/skills-library/ecommerce/webhook-idempotency-patterns.md +253 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/ci.yml +558 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/prompt-gallery.yml +448 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/release.yml +42 -0
- package/skills-library/excalidraw-diagrams/.github/workflows/test-reusable-ci.yml +25 -0
- package/skills-library/excalidraw-diagrams/CLAUDE.md +57 -0
- package/skills-library/excalidraw-diagrams/LICENSE +21 -0
- package/skills-library/excalidraw-diagrams/README.md +178 -0
- package/skills-library/excalidraw-diagrams/SKILL.md +715 -0
- package/skills-library/form-solutions/BUTTON_TYPE_FORM_SUBMISSION.md +336 -0
- package/skills-library/form-solutions/FILLABLE_PDF_IMPLEMENTATION.md +226 -0
- package/skills-library/form-solutions/SURVEYJS_QUESTIONNAIRE_SYSTEM.md +367 -0
- package/skills-library/form-solutions/tiptap-minimal-setup.md +690 -0
- package/skills-library/frontend/scholarly-classification-bubble-map.md +149 -0
- package/skills-library/infrastructure/ci-cd-pipeline-builder.md +517 -0
- package/skills-library/infrastructure/observability-designer.md +264 -0
- package/skills-library/infrastructure/performance-profiler.md +621 -0
- package/skills-library/installer-wizard-patterns.md +249 -0
- package/skills-library/integrations/CLAUDE_CODE_TOKEN_ANALYTICS.md +160 -0
- package/skills-library/integrations/CONFIGURABLE_AI_PROVIDER_SELECTION.md +728 -0
- package/skills-library/integrations/SOCKET_IO_BROADCAST_ALL_VS_ROOM.md +141 -0
- package/skills-library/integrations/VIRTUAL_MEETINGS_IMPLEMENTATION.md +374 -0
- package/skills-library/integrations/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
- package/skills-library/integrations/YOUTUBE_API_SETUP.md +141 -0
- package/skills-library/integrations/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
- package/skills-library/integrations/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
- package/skills-library/integrations/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
- package/skills-library/integrations/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
- package/skills-library/integrations/ai-ml/GEMINI_AI_RAG_PIPELINE_COMPLETE_GUIDE.md +195 -0
- package/skills-library/integrations/ai-ml/GEMINI_IMAGE_GENERATION_SETUP.md +64 -0
- package/skills-library/integrations/cloudflare/cloudflare-turnstile-debugging.md +202 -0
- package/skills-library/integrations/cloudflare/cloudflare-turnstile-implementation.md +476 -0
- package/skills-library/integrations/cloudflare-turnstile-debugging.md +202 -0
- package/skills-library/integrations/cloudflare-turnstile-implementation.md +476 -0
- package/skills-library/integrations/ghost-creator-monetization-pattern.md +454 -0
- package/skills-library/integrations/headless-cms-architecture.md +484 -0
- package/skills-library/integrations/headless-cms-stack-selection.md +183 -0
- package/skills-library/integrations/payload-cms-patterns.md +674 -0
- package/skills-library/integrations/realtimestt-openwakeword-cuda-windows.md +229 -0
- package/skills-library/integrations/rss-podcast-integration.md +300 -0
- package/skills-library/integrations/wordpress/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
- package/skills-library/integrations/youtube/YOUTUBE_API_SETUP.md +141 -0
- package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
- package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
- package/skills-library/integrations/youtube/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
- package/skills-library/integrations/youtube/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
- package/skills-library/marketing/campaign-analytics.md +97 -0
- package/skills-library/marketing/content-creator.md +105 -0
- package/skills-library/marketing/marketing-strategy-pmm.md +94 -0
- package/skills-library/marketing/social-media-analyzer.md +81 -0
- package/skills-library/methodology/ADVANCED_ORCHESTRATION_PATTERNS.md +401 -0
- package/skills-library/methodology/AGENT_SELF_IMPROVEMENT_LOOP.md +179 -0
- package/skills-library/methodology/BREATH_BASED_PARALLEL_EXECUTION.md +1 -1
- package/skills-library/methodology/CLEANSING_CYCLE.md +358 -0
- package/skills-library/methodology/CONFIDENCE_ANNOTATION_PATTERN.md +143 -0
- package/skills-library/methodology/CRITICAL_PATTERNS_DOCUMENTATION_COMPLETE.md +204 -0
- package/skills-library/methodology/DELIVERABLES_SUMMARY.md +341 -0
- package/skills-library/methodology/DIFFICULTY_AWARE_AGENT_ROUTING.md +252 -0
- package/skills-library/methodology/EVOLUTIONARY_SKILL_SYNTHESIS.md +219 -0
- package/skills-library/methodology/GLOMERULUS_DECISION_GATE.md +223 -0
- package/skills-library/methodology/HIBERNATION_SYSTEM.md +231 -0
- package/skills-library/methodology/INSTRUMENTATION_OVER_RESTRICTION.md +192 -0
- package/skills-library/methodology/MASTER_COMPLETION_SUMMARY.md +444 -0
- package/skills-library/methodology/MASTER_SESSION_COMPLETION.md +743 -0
- package/skills-library/methodology/MERN_QUICK_REFERENCE.md +358 -0
- package/skills-library/methodology/ORGAN_AGENT_MAPPING.md +177 -0
- package/skills-library/methodology/PARALLEL_WAVE_BASED_REFACTORING.md +440 -0
- package/skills-library/methodology/QUICK_REFERENCE.md +358 -0
- package/skills-library/methodology/SDFT_ONPOLICY_SELF_DISTILLATION.md +186 -0
- package/skills-library/methodology/SELF_QUESTIONING_TASK_GENERATION.md +270 -0
- package/skills-library/methodology/SESSION_COMPLETION_SUMMARY.md +304 -0
- package/skills-library/methodology/SESSION_SUMMARY.md +432 -0
- package/skills-library/methodology/WARRIOR_WORKFLOW_DEBUGGING_PROTOCOL.md +252 -0
- package/skills-library/methodology/tech-debt-tracker.md +570 -0
- package/skills-library/parallel-debug/SKILL.md +60 -0
- package/skills-library/patterns-standards/API_PATTERN_FIX_SUMMARY.md +236 -0
- package/skills-library/patterns-standards/BATCH_OPERATIONS_WITH_PROGRESS_MODAL.md +362 -0
- package/skills-library/patterns-standards/CRITICAL_CODING_PATTERNS.md +639 -0
- package/skills-library/patterns-standards/DARK_MODE_MODAL_VISIBILITY.md +258 -0
- package/skills-library/patterns-standards/ERROR_RESILIENCE_IMPLEMENTATION.md +375 -0
- package/skills-library/patterns-standards/ES_MODULE_IMPORT_HOISTING_DOTENV.md +298 -0
- package/skills-library/patterns-standards/NESTED_BACKDROP_FILTER_CSS_ARTIFACT_FIX.md +76 -0
- package/skills-library/patterns-standards/ORDERED_DETECTOR_PIPELINE_GRACEFUL_FALLBACK.md +333 -0
- package/skills-library/patterns-standards/PHASE_IMPORT_ERROR_DEBUGGING.md +271 -0
- package/skills-library/patterns-standards/PYNPUT_GLOBAL_HOTKEY_VK_MATCHING.md +252 -0
- package/skills-library/patterns-standards/REACT_USEEFFECT_CASCADE_RESET_FIX.md +132 -0
- package/skills-library/patterns-standards/SUBMENU_HOVER_DROPDOWN_PATTERN.md +225 -0
- package/skills-library/patterns-standards/TAILWIND_TEXT_VISIBILITY_OVERRIDE.md +322 -0
- package/skills-library/patterns-standards/THEME_AWARE_CSS_VARIABLES_PATTERN.md +209 -0
- package/skills-library/patterns-standards/THEME_USER_OBJECT_PROPERTY_NAMING.md +194 -0
- package/skills-library/patterns-standards/TOOLTIP_BLOCKING_CLICKS_FIX.md +267 -0
- package/skills-library/patterns-standards/claude-code-plugin-structure.md +235 -0
- package/skills-library/patterns-standards/react-i18next-setup.md +429 -0
- package/skills-library/patterns-standards/thesys-c1-generative-ui-integration.md +967 -0
- package/skills-library/plugin-development/CLAUDE_CODE_COMMAND_REGISTRATION_SILENT_FAILURE.md +315 -0
- package/skills-library/plugin-development/plugin-command-namespace-vs-global.md +390 -0
- package/skills-library/plugin-development/plugin-doc-auto-generation.md +172 -0
- package/skills-library/security/GITHUB_REPO_SECURITY_AUDIT.md +115 -0
- package/skills-library/security/admin-deletion-safety.md +396 -0
- package/skills-library/security/application-vuln-patterns.md +477 -0
- package/skills-library/security/env-secrets-manager.md +686 -0
- package/skills-library/security/secure-ai-application-templates.md +347 -0
- package/skills-library/security/sql-injection-prevention-postgresjs.md +151 -0
- package/skills-library/supabase-connection-pooler-fix.md +102 -0
- package/skills-library/system-context/POWERSHELL_BASH_INTEROP.md +82 -0
- package/skills-library/system-context/SERVICE_LIFECYCLE_MANAGEMENT.md +119 -0
- package/skills-library/system-context/SKILL.md +40 -0
- package/skills-library/system-context/WINDOWS_DEV_ENVIRONMENT.md +73 -0
- package/skills-library/testing/E2E_PLAYWRIGHT_PATTERNS.md +99 -0
- package/skills-library/testing/INTEGRATION_TEST_STRATEGY.md +82 -0
- package/skills-library/testing/RED_GREEN_BUGFIX_GATE.md +203 -0
- package/skills-library/testing/TEST_DATA_MANAGEMENT.md +69 -0
- package/skills-library/testing/VITEST_UNIT_TEST_PATTERNS.md +75 -0
- package/skills-library/testing/playwright-api-security-tests.md +202 -0
- package/skills-library/toolbox/SKILL.md +84 -0
- package/skills-library/toolbox/code-graph-and-web-scraping-mcps.md +237 -0
- package/skills-library/ui-ux-pro-max/ACCESSIBILITY_ESSENTIALS.md +115 -0
- package/skills-library/ui-ux-pro-max/DESIGN_SYSTEM_SCAFFOLDING.md +133 -0
- package/skills-library/ui-ux-pro-max/RESPONSIVE_LAYOUT_PATTERNS.md +119 -0
- package/skills-library/ui-ux-pro-max/SKILL.md +386 -0
- package/skills-library/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills-library/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills-library/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills-library/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills-library/ui-ux-pro-max/data/products.csv +97 -0
- package/skills-library/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills-library/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills-library/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills-library/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills-library/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills-library/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills-library/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills-library/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills-library/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills-library/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills-library/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills-library/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills-library/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills-library/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills-library/wordpress-style-theme-components.md +1526 -0
- package/templates/ASSUMPTIONS.md +1 -1
- package/templates/DECISION_LOG.md +0 -1
- package/templates/phase-prompt.md +1 -1
- package/templates/phoenix-comparison.md +6 -6
- package/templates/skill-api-integration.md +106 -0
- package/templates/skill-architecture-pattern.md +92 -0
- package/templates/skill-debug-pattern.md +98 -0
- package/templates/skill-devops-recipe.md +107 -0
- package/templates/skill-general.md +65 -0
- package/templates/skill-ui-component.md +113 -0
- package/tools/uat-runner.py +179 -0
- package/version.json +7 -3
- package/workflows/handoff-session.md +2 -2
- package/workflows/new-project.md +2 -2
- package/workflows/plan-phase.md +1 -1
- package/.claude-plugin/plugin.json +0 -64
- package/skills-library/_general/methodology/LIVE_BREADCRUMB_PROTOCOL.md +0 -242
- package/skills-library/_general/methodology/llm-judge-memory-crud.md +0 -241
- package/skills-library/methodology/REFLEXION_MEMORY_PATTERN.md +0 -183
- package/skills-library/methodology/RESEARCH_BACKED_WORKFLOW_UPGRADE.md +0 -263
- package/skills-library/methodology/SABBATH_REST_PATTERN.md +0 -267
- package/skills-library/methodology/STONE_AND_SCAFFOLD.md +0 -220
- package/skills-library/specialists/api-architecture/api-designer.md +0 -49
- package/skills-library/specialists/api-architecture/graphql-architect.md +0 -49
- package/skills-library/specialists/api-architecture/mcp-developer.md +0 -51
- package/skills-library/specialists/api-architecture/microservices-architect.md +0 -50
- package/skills-library/specialists/api-architecture/websocket-engineer.md +0 -48
- package/skills-library/specialists/backend/django-expert.md +0 -52
- package/skills-library/specialists/backend/fastapi-expert.md +0 -52
- package/skills-library/specialists/backend/laravel-specialist.md +0 -52
- package/skills-library/specialists/backend/nestjs-expert.md +0 -51
- package/skills-library/specialists/backend/rails-expert.md +0 -53
- package/skills-library/specialists/backend/spring-boot-engineer.md +0 -56
- package/skills-library/specialists/data-ml/fine-tuning-expert.md +0 -48
- package/skills-library/specialists/data-ml/ml-pipeline.md +0 -47
- package/skills-library/specialists/data-ml/pandas-pro.md +0 -47
- package/skills-library/specialists/data-ml/rag-architect.md +0 -51
- package/skills-library/specialists/data-ml/spark-engineer.md +0 -47
- package/skills-library/specialists/frontend/angular-architect.md +0 -52
- package/skills-library/specialists/frontend/flutter-expert.md +0 -51
- package/skills-library/specialists/frontend/nextjs-developer.md +0 -54
- package/skills-library/specialists/frontend/react-native-expert.md +0 -50
- package/skills-library/specialists/frontend/vue-expert.md +0 -51
- package/skills-library/specialists/infrastructure/chaos-engineer.md +0 -74
- package/skills-library/specialists/infrastructure/cloud-architect.md +0 -70
- package/skills-library/specialists/infrastructure/database-optimizer.md +0 -64
- package/skills-library/specialists/infrastructure/devops-engineer.md +0 -70
- package/skills-library/specialists/infrastructure/kubernetes-specialist.md +0 -52
- package/skills-library/specialists/infrastructure/monitoring-expert.md +0 -70
- package/skills-library/specialists/infrastructure/sre-engineer.md +0 -70
- package/skills-library/specialists/infrastructure/terraform-engineer.md +0 -51
- package/skills-library/specialists/languages/cpp-pro.md +0 -74
- package/skills-library/specialists/languages/csharp-developer.md +0 -69
- package/skills-library/specialists/languages/dotnet-core-expert.md +0 -54
- package/skills-library/specialists/languages/golang-pro.md +0 -51
- package/skills-library/specialists/languages/java-architect.md +0 -49
- package/skills-library/specialists/languages/javascript-pro.md +0 -68
- package/skills-library/specialists/languages/kotlin-specialist.md +0 -68
- package/skills-library/specialists/languages/php-pro.md +0 -49
- package/skills-library/specialists/languages/python-pro.md +0 -52
- package/skills-library/specialists/languages/react-expert.md +0 -51
- package/skills-library/specialists/languages/rust-engineer.md +0 -50
- package/skills-library/specialists/languages/sql-pro.md +0 -56
- package/skills-library/specialists/languages/swift-expert.md +0 -69
- package/skills-library/specialists/languages/typescript-pro.md +0 -51
- package/skills-library/specialists/platform/atlassian-mcp.md +0 -52
- package/skills-library/specialists/platform/embedded-systems.md +0 -53
- package/skills-library/specialists/platform/game-developer.md +0 -53
- package/skills-library/specialists/platform/salesforce-developer.md +0 -53
- package/skills-library/specialists/platform/shopify-expert.md +0 -49
- package/skills-library/specialists/platform/wordpress-pro.md +0 -49
- package/skills-library/specialists/quality/code-documenter.md +0 -51
- package/skills-library/specialists/quality/code-reviewer.md +0 -67
- package/skills-library/specialists/quality/debugging-wizard.md +0 -51
- package/skills-library/specialists/quality/fullstack-guardian.md +0 -51
- package/skills-library/specialists/quality/legacy-modernizer.md +0 -50
- package/skills-library/specialists/quality/playwright-expert.md +0 -65
- package/skills-library/specialists/quality/spec-miner.md +0 -56
- package/skills-library/specialists/quality/test-master.md +0 -65
- package/skills-library/specialists/security/secure-code-guardian.md +0 -55
- package/skills-library/specialists/security/security-reviewer.md +0 -53
- package/skills-library/specialists/workflow/architecture-designer.md +0 -53
- package/skills-library/specialists/workflow/cli-developer.md +0 -70
- package/skills-library/specialists/workflow/feature-forge.md +0 -65
- package/skills-library/specialists/workflow/prompt-engineer.md +0 -54
- package/skills-library/specialists/workflow/the-fool.md +0 -62
- /package/skills-library/{performance → _general/performance}/cache-augmented-generation.md +0 -0
- /package/skills-library/{debugging → parallel-debug}/FAILURE_TAXONOMY_CLASSIFICATION.md +0 -0
- /package/skills-library/{debugging → parallel-debug}/THREE_AGENT_HYPOTHESIS_DEBUGGING.md +0 -0
|
@@ -0,0 +1,1841 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: podcast-script-generation
|
|
3
|
+
category: creative-multimedia
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
contributed: 2026-03-10
|
|
6
|
+
contributor: dominion-flow-research
|
|
7
|
+
last_updated: 2026-03-10
|
|
8
|
+
tags: [podcast, script, dialogue, multi-agent, content-generation, rag, document-analysis]
|
|
9
|
+
difficulty: hard
|
|
10
|
+
---
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
AI-powered podcast script generation from documents using a multi-agent pattern. Transforms PDFs, articles, YouTube transcripts, and raw text into structured, natural-sounding podcast scripts with multiple speaker formats: two-speaker deep-dive, panel discussion, solo narration, and debate. Uses the PodAgent three-agent architecture (Host, Guest, Writer) with faithfulness verification to prevent hallucination in long-form output.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Converting research papers, reports, or articles into podcast-format audio content
|
|
18
|
+
- Building a "NotebookLM-style" document-to-podcast feature in your app
|
|
19
|
+
- Generating interview scripts from source material for TTS pipelines
|
|
20
|
+
- Creating educational audio content from textbooks or lesson plans
|
|
21
|
+
- Repurposing blog posts, sermons, or documentation into conversational audio
|
|
22
|
+
- Generating script drafts for human hosts to review and record
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## The PodAgent Multi-Agent Pattern
|
|
27
|
+
|
|
28
|
+
Based on PodAgent (ACL 2025, arXiv 2503.00455) -- 87.4% voice-role matching accuracy.
|
|
29
|
+
|
|
30
|
+
The key insight: direct single-prompt generation produces flat, monotonous scripts. Splitting the task across three specialized agents produces natural dialogue with distinct voices, better pacing, and verifiable faithfulness to source material.
|
|
31
|
+
|
|
32
|
+
### Architecture
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Source Documents
|
|
36
|
+
|
|
|
37
|
+
v
|
|
38
|
+
+-------------------+
|
|
39
|
+
| Host Agent | Formulates questions, creates guest profiles,
|
|
40
|
+
| (Interviewer) | drives conversation flow, sets topic transitions
|
|
41
|
+
+-------------------+
|
|
42
|
+
|
|
|
43
|
+
| questions + guest profile
|
|
44
|
+
v
|
|
45
|
+
+-------------------+
|
|
46
|
+
| Guest Agent(s) | Provides domain expertise from source documents,
|
|
47
|
+
| (Expert) | answers Host's questions with citations
|
|
48
|
+
+-------------------+
|
|
49
|
+
|
|
|
50
|
+
| raw Host-Guest dialogue
|
|
51
|
+
v
|
|
52
|
+
+-------------------+
|
|
53
|
+
| Writer Agent | Composes final script from dialogue, adds
|
|
54
|
+
| (Producer) | transitions, pacing cues, tone directions
|
|
55
|
+
+-------------------+
|
|
56
|
+
|
|
|
57
|
+
v
|
|
58
|
+
+-------------------+
|
|
59
|
+
| Faithfulness | Verifies every claim against source text,
|
|
60
|
+
| Checker | flags hallucinations, suggests corrections
|
|
61
|
+
+-------------------+
|
|
62
|
+
|
|
|
63
|
+
v
|
|
64
|
+
Structured PodcastScript (JSON)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### TypeScript Types
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ─── Core Types ─────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
interface PodcastScript {
|
|
73
|
+
title: string;
|
|
74
|
+
duration: '5min' | '15min' | '30min' | '60min';
|
|
75
|
+
speakers: Speaker[];
|
|
76
|
+
segments: ScriptSegment[];
|
|
77
|
+
metadata: PodcastMetadata;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface Speaker {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
role: 'host' | 'guest' | 'narrator';
|
|
84
|
+
voiceProfile: VoiceProfile;
|
|
85
|
+
expertise?: string; // For guests: their domain knowledge area
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface VoiceProfile {
|
|
89
|
+
tone: 'warm' | 'authoritative' | 'curious' | 'measured' | 'energetic';
|
|
90
|
+
pace: 'slow' | 'moderate' | 'fast';
|
|
91
|
+
style: 'conversational' | 'formal' | 'storytelling' | 'academic';
|
|
92
|
+
ttsVoiceId?: string; // Provider-specific voice ID for TTS
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface ScriptSegment {
|
|
96
|
+
speaker: string; // Speaker ID reference
|
|
97
|
+
text: string; // The spoken words
|
|
98
|
+
direction: string; // e.g., "(enthusiastically)", "(thoughtfully)"
|
|
99
|
+
timing?: number; // Estimated seconds for this segment
|
|
100
|
+
segmentType: SegmentType;
|
|
101
|
+
sourceReference?: string; // Citation back to source document chunk
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type SegmentType =
|
|
105
|
+
| 'intro'
|
|
106
|
+
| 'question'
|
|
107
|
+
| 'answer'
|
|
108
|
+
| 'transition'
|
|
109
|
+
| 'deep-dive'
|
|
110
|
+
| 'anecdote'
|
|
111
|
+
| 'summary'
|
|
112
|
+
| 'outro'
|
|
113
|
+
| 'ad-break';
|
|
114
|
+
|
|
115
|
+
interface PodcastMetadata {
|
|
116
|
+
sourceDocuments: string[]; // File paths or URLs of source material
|
|
117
|
+
generatedAt: string; // ISO timestamp
|
|
118
|
+
wordCount: number;
|
|
119
|
+
estimatedDuration: number; // Seconds
|
|
120
|
+
format: PodcastFormat;
|
|
121
|
+
faithfulnessScore: number; // 0-1, from verification step
|
|
122
|
+
keyTopics: string[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type PodcastFormat = 'two-speaker' | 'panel' | 'solo-narration' | 'debate';
|
|
126
|
+
|
|
127
|
+
// ─── Pipeline Types ─────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
interface DocumentChunk {
|
|
130
|
+
id: string;
|
|
131
|
+
text: string;
|
|
132
|
+
source: string; // File name or URL
|
|
133
|
+
pageNumber?: number;
|
|
134
|
+
sectionTitle?: string;
|
|
135
|
+
embedding?: number[]; // For semantic search during generation
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface KeyPoint {
|
|
139
|
+
point: string;
|
|
140
|
+
importance: 'critical' | 'important' | 'supplementary';
|
|
141
|
+
sourceChunkIds: string[]; // Which chunks support this point
|
|
142
|
+
suggestedQuestions: string[]; // Questions the Host could ask about this
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface PodcastOutline {
|
|
146
|
+
title: string;
|
|
147
|
+
hook: string; // Opening hook to grab listener attention
|
|
148
|
+
segments: OutlineSegment[];
|
|
149
|
+
closingThought: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface OutlineSegment {
|
|
153
|
+
topic: string;
|
|
154
|
+
keyPoints: string[];
|
|
155
|
+
estimatedDuration: number; // Seconds
|
|
156
|
+
transitionFrom?: string; // How to flow from previous segment
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface FaithfulnessReport {
|
|
160
|
+
overallScore: number; // 0-1
|
|
161
|
+
segmentScores: Array<{
|
|
162
|
+
segmentIndex: number;
|
|
163
|
+
score: number;
|
|
164
|
+
claims: Array<{
|
|
165
|
+
claim: string;
|
|
166
|
+
supported: boolean;
|
|
167
|
+
sourceEvidence?: string;
|
|
168
|
+
correction?: string;
|
|
169
|
+
}>;
|
|
170
|
+
}>;
|
|
171
|
+
flaggedSegments: number[]; // Indices of segments scoring below 0.7
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Document-to-Script Pipeline
|
|
178
|
+
|
|
179
|
+
### Complete Pipeline (6 Steps)
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Step 1: Document Ingestion
|
|
183
|
+
- Parse PDF/DOCX/URL/YouTube transcript
|
|
184
|
+
- Extract raw text, clean formatting artifacts
|
|
185
|
+
- Preserve section structure for context
|
|
186
|
+
|
|
187
|
+
Step 2: Semantic Chunking
|
|
188
|
+
- Split by semantic boundaries (not fixed-size)
|
|
189
|
+
- 300-500 token chunks with 50-token overlap
|
|
190
|
+
- Preserve paragraph/section context in metadata
|
|
191
|
+
|
|
192
|
+
Step 3: Key Point Extraction
|
|
193
|
+
- AI identifies top N discussion-worthy points
|
|
194
|
+
- Rank by importance and listener interest
|
|
195
|
+
- Generate potential questions per point
|
|
196
|
+
|
|
197
|
+
Step 4: Outline Generation
|
|
198
|
+
- Structure: hook --> intro --> segments --> summary --> outro
|
|
199
|
+
- Assign duration budgets per segment
|
|
200
|
+
- Plan transitions between topics
|
|
201
|
+
|
|
202
|
+
Step 5: Multi-Agent Script Generation
|
|
203
|
+
- Host Agent generates questions and flow
|
|
204
|
+
- Guest Agent provides expert responses from source
|
|
205
|
+
- Writer Agent polishes into final script
|
|
206
|
+
- Generate PER-CHUNK, not all at once (see Hallucination Fix)
|
|
207
|
+
|
|
208
|
+
Step 6: Faithfulness Verification
|
|
209
|
+
- Check every claim against source documents
|
|
210
|
+
- Score each segment independently
|
|
211
|
+
- Flag and correct hallucinations before output
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Step 1: Document Ingestion
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { promises as fs } from 'fs';
|
|
218
|
+
import path from 'path';
|
|
219
|
+
|
|
220
|
+
// ─── Document Parser ────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
interface ParsedDocument {
|
|
223
|
+
title: string;
|
|
224
|
+
text: string;
|
|
225
|
+
sections: Array<{ heading: string; content: string }>;
|
|
226
|
+
metadata: { source: string; pageCount?: number; wordCount: number };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function parseDocument(filePath: string): Promise<ParsedDocument> {
|
|
230
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
231
|
+
|
|
232
|
+
switch (ext) {
|
|
233
|
+
case '.pdf':
|
|
234
|
+
return parsePDF(filePath);
|
|
235
|
+
case '.txt':
|
|
236
|
+
case '.md':
|
|
237
|
+
return parseText(filePath);
|
|
238
|
+
case '.html':
|
|
239
|
+
return parseHTML(filePath);
|
|
240
|
+
default:
|
|
241
|
+
throw new Error(`Unsupported file type: ${ext}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function parseText(filePath: string): Promise<ParsedDocument> {
|
|
246
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
247
|
+
const lines = raw.split('\n');
|
|
248
|
+
const title = lines[0]?.replace(/^#+\s*/, '') || path.basename(filePath);
|
|
249
|
+
|
|
250
|
+
// Extract sections by markdown headers or blank-line separation
|
|
251
|
+
const sections: Array<{ heading: string; content: string }> = [];
|
|
252
|
+
let currentHeading = 'Introduction';
|
|
253
|
+
let currentContent: string[] = [];
|
|
254
|
+
|
|
255
|
+
for (const line of lines) {
|
|
256
|
+
if (line.match(/^#{1,3}\s+/)) {
|
|
257
|
+
if (currentContent.length > 0) {
|
|
258
|
+
sections.push({ heading: currentHeading, content: currentContent.join('\n').trim() });
|
|
259
|
+
}
|
|
260
|
+
currentHeading = line.replace(/^#+\s*/, '');
|
|
261
|
+
currentContent = [];
|
|
262
|
+
} else {
|
|
263
|
+
currentContent.push(line);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (currentContent.length > 0) {
|
|
267
|
+
sections.push({ heading: currentHeading, content: currentContent.join('\n').trim() });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
title,
|
|
272
|
+
text: raw,
|
|
273
|
+
sections,
|
|
274
|
+
metadata: {
|
|
275
|
+
source: filePath,
|
|
276
|
+
wordCount: raw.split(/\s+/).length,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function parsePDF(filePath: string): Promise<ParsedDocument> {
|
|
282
|
+
// Use pdf-parse (npm install pdf-parse)
|
|
283
|
+
const pdfParse = (await import('pdf-parse')).default;
|
|
284
|
+
const buffer = await fs.readFile(filePath);
|
|
285
|
+
const data = await pdfParse(buffer);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
title: data.info?.Title || path.basename(filePath, '.pdf'),
|
|
289
|
+
text: data.text,
|
|
290
|
+
sections: [{ heading: 'Full Document', content: data.text }],
|
|
291
|
+
metadata: {
|
|
292
|
+
source: filePath,
|
|
293
|
+
pageCount: data.numpages,
|
|
294
|
+
wordCount: data.text.split(/\s+/).length,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function parseHTML(filePath: string): Promise<ParsedDocument> {
|
|
300
|
+
// Use cheerio for HTML parsing (npm install cheerio)
|
|
301
|
+
const cheerio = await import('cheerio');
|
|
302
|
+
const html = await fs.readFile(filePath, 'utf-8');
|
|
303
|
+
const $ = cheerio.load(html);
|
|
304
|
+
|
|
305
|
+
// Remove scripts, styles, nav, footer
|
|
306
|
+
$('script, style, nav, footer, header, aside').remove();
|
|
307
|
+
|
|
308
|
+
const title = $('h1').first().text() || $('title').text() || path.basename(filePath);
|
|
309
|
+
const text = $('body').text().replace(/\s+/g, ' ').trim();
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
title,
|
|
313
|
+
text,
|
|
314
|
+
sections: [{ heading: 'Content', content: text }],
|
|
315
|
+
metadata: {
|
|
316
|
+
source: filePath,
|
|
317
|
+
wordCount: text.split(/\s+/).length,
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Step 2: Semantic Chunking
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// ─── Semantic Chunking ──────────────────────────────────────────────────────
|
|
327
|
+
// Critical: Use semantic boundaries, NOT fixed-size windows.
|
|
328
|
+
// Fixed-size chunking splits mid-sentence and loses context.
|
|
329
|
+
|
|
330
|
+
interface ChunkConfig {
|
|
331
|
+
targetTokens: number; // Target chunk size (300-500 tokens)
|
|
332
|
+
overlapTokens: number; // Overlap between chunks (50 tokens)
|
|
333
|
+
preserveParagraphs: boolean;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function semanticChunk(
|
|
337
|
+
document: ParsedDocument,
|
|
338
|
+
config: ChunkConfig = { targetTokens: 400, overlapTokens: 50, preserveParagraphs: true }
|
|
339
|
+
): DocumentChunk[] {
|
|
340
|
+
const chunks: DocumentChunk[] = [];
|
|
341
|
+
let chunkIndex = 0;
|
|
342
|
+
|
|
343
|
+
for (const section of document.sections) {
|
|
344
|
+
// Split by paragraph boundaries first
|
|
345
|
+
const paragraphs = section.content
|
|
346
|
+
.split(/\n\n+/)
|
|
347
|
+
.map(p => p.trim())
|
|
348
|
+
.filter(p => p.length > 0);
|
|
349
|
+
|
|
350
|
+
let currentChunk: string[] = [];
|
|
351
|
+
let currentTokenCount = 0;
|
|
352
|
+
|
|
353
|
+
for (const paragraph of paragraphs) {
|
|
354
|
+
const paragraphTokens = estimateTokens(paragraph);
|
|
355
|
+
|
|
356
|
+
// If adding this paragraph exceeds target, finalize current chunk
|
|
357
|
+
if (currentTokenCount + paragraphTokens > config.targetTokens && currentChunk.length > 0) {
|
|
358
|
+
const chunkText = currentChunk.join('\n\n');
|
|
359
|
+
chunks.push({
|
|
360
|
+
id: `chunk-${chunkIndex++}`,
|
|
361
|
+
text: chunkText,
|
|
362
|
+
source: document.metadata.source,
|
|
363
|
+
sectionTitle: section.heading,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Keep overlap: last paragraph carries into next chunk
|
|
367
|
+
if (config.overlapTokens > 0) {
|
|
368
|
+
const lastParagraph = currentChunk[currentChunk.length - 1];
|
|
369
|
+
currentChunk = [lastParagraph];
|
|
370
|
+
currentTokenCount = estimateTokens(lastParagraph);
|
|
371
|
+
} else {
|
|
372
|
+
currentChunk = [];
|
|
373
|
+
currentTokenCount = 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
currentChunk.push(paragraph);
|
|
378
|
+
currentTokenCount += paragraphTokens;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Finalize remaining content in section
|
|
382
|
+
if (currentChunk.length > 0) {
|
|
383
|
+
chunks.push({
|
|
384
|
+
id: `chunk-${chunkIndex++}`,
|
|
385
|
+
text: currentChunk.join('\n\n'),
|
|
386
|
+
source: document.metadata.source,
|
|
387
|
+
sectionTitle: section.heading,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return chunks;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function estimateTokens(text: string): number {
|
|
396
|
+
// Rough estimate: 1 token per ~4 characters for English text
|
|
397
|
+
return Math.ceil(text.length / 4);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Step 3: Key Point Extraction
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
405
|
+
|
|
406
|
+
// ─── Key Point Extraction ───────────────────────────────────────────────────
|
|
407
|
+
|
|
408
|
+
async function extractKeyPoints(
|
|
409
|
+
chunks: DocumentChunk[],
|
|
410
|
+
targetPoints: number,
|
|
411
|
+
client: Anthropic
|
|
412
|
+
): Promise<KeyPoint[]> {
|
|
413
|
+
// Process chunks in batches to stay within context limits
|
|
414
|
+
const batchSize = 10;
|
|
415
|
+
const allPoints: KeyPoint[] = [];
|
|
416
|
+
|
|
417
|
+
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
418
|
+
const batch = chunks.slice(i, i + batchSize);
|
|
419
|
+
const batchText = batch.map(c =>
|
|
420
|
+
`[${c.id}] (Section: ${c.sectionTitle || 'N/A'})\n${c.text}`
|
|
421
|
+
).join('\n\n---\n\n');
|
|
422
|
+
|
|
423
|
+
const response = await client.messages.create({
|
|
424
|
+
model: 'claude-sonnet-4-20250514',
|
|
425
|
+
max_tokens: 4096,
|
|
426
|
+
messages: [{
|
|
427
|
+
role: 'user',
|
|
428
|
+
content: `Analyze these document chunks and extract the most discussion-worthy points for a podcast conversation.
|
|
429
|
+
|
|
430
|
+
DOCUMENT CHUNKS:
|
|
431
|
+
${batchText}
|
|
432
|
+
|
|
433
|
+
For each point, provide:
|
|
434
|
+
1. A clear statement of the point
|
|
435
|
+
2. Its importance level (critical / important / supplementary)
|
|
436
|
+
3. Which chunk IDs support it
|
|
437
|
+
4. 2-3 questions a podcast host could ask about this point
|
|
438
|
+
|
|
439
|
+
Return JSON array:
|
|
440
|
+
[
|
|
441
|
+
{
|
|
442
|
+
"point": "statement of the key point",
|
|
443
|
+
"importance": "critical|important|supplementary",
|
|
444
|
+
"sourceChunkIds": ["chunk-0", "chunk-1"],
|
|
445
|
+
"suggestedQuestions": ["Question 1?", "Question 2?"]
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
Extract ${Math.ceil(targetPoints / Math.ceil(chunks.length / batchSize))} points from this batch.
|
|
450
|
+
Return ONLY valid JSON.`,
|
|
451
|
+
}],
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '[]';
|
|
455
|
+
const parsed = JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
456
|
+
allPoints.push(...parsed);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Rank and deduplicate
|
|
460
|
+
const ranked = allPoints
|
|
461
|
+
.sort((a, b) => {
|
|
462
|
+
const importanceOrder = { critical: 0, important: 1, supplementary: 2 };
|
|
463
|
+
return importanceOrder[a.importance] - importanceOrder[b.importance];
|
|
464
|
+
})
|
|
465
|
+
.slice(0, targetPoints);
|
|
466
|
+
|
|
467
|
+
return ranked;
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Step 4: Outline Generation
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// ─── Outline Generation ─────────────────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
async function generateOutline(
|
|
477
|
+
keyPoints: KeyPoint[],
|
|
478
|
+
duration: '5min' | '15min' | '30min' | '60min',
|
|
479
|
+
format: PodcastFormat,
|
|
480
|
+
client: Anthropic
|
|
481
|
+
): Promise<PodcastOutline> {
|
|
482
|
+
const durationMap = {
|
|
483
|
+
'5min': { seconds: 300, points: 3, segmentCount: 3 },
|
|
484
|
+
'15min': { seconds: 900, points: 7, segmentCount: 5 },
|
|
485
|
+
'30min': { seconds: 1800, points: 12, segmentCount: 8 },
|
|
486
|
+
'60min': { seconds: 3600, points: 20, segmentCount: 12 },
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const config = durationMap[duration];
|
|
490
|
+
const selectedPoints = keyPoints.slice(0, config.points);
|
|
491
|
+
|
|
492
|
+
const response = await client.messages.create({
|
|
493
|
+
model: 'claude-sonnet-4-20250514',
|
|
494
|
+
max_tokens: 4096,
|
|
495
|
+
messages: [{
|
|
496
|
+
role: 'user',
|
|
497
|
+
content: `Create a podcast outline for a ${duration} ${format} episode.
|
|
498
|
+
|
|
499
|
+
KEY POINTS TO COVER:
|
|
500
|
+
${selectedPoints.map((p, i) => `${i + 1}. [${p.importance}] ${p.point}`).join('\n')}
|
|
501
|
+
|
|
502
|
+
FORMAT: ${format}
|
|
503
|
+
TARGET DURATION: ${config.seconds} seconds
|
|
504
|
+
TARGET SEGMENTS: ${config.segmentCount}
|
|
505
|
+
|
|
506
|
+
Create an outline with:
|
|
507
|
+
- A compelling hook (first 15-30 seconds to grab attention)
|
|
508
|
+
- Logical flow between topics with smooth transitions
|
|
509
|
+
- Duration budget per segment (must sum to ~${config.seconds} seconds)
|
|
510
|
+
- A memorable closing thought
|
|
511
|
+
|
|
512
|
+
Return JSON:
|
|
513
|
+
{
|
|
514
|
+
"title": "Episode title",
|
|
515
|
+
"hook": "Opening hook text",
|
|
516
|
+
"segments": [
|
|
517
|
+
{
|
|
518
|
+
"topic": "Segment topic",
|
|
519
|
+
"keyPoints": ["point 1", "point 2"],
|
|
520
|
+
"estimatedDuration": 120,
|
|
521
|
+
"transitionFrom": "How to transition from previous segment"
|
|
522
|
+
}
|
|
523
|
+
],
|
|
524
|
+
"closingThought": "Memorable closing"
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
Return ONLY valid JSON.`,
|
|
528
|
+
}],
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '{}';
|
|
532
|
+
return JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Step 5: Multi-Agent Script Generation
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
// ─── Multi-Agent Script Generation ──────────────────────────────────────────
|
|
540
|
+
// CRITICAL: Generate per-segment, not all at once.
|
|
541
|
+
// See "The Hallucination Chunking Fix" section below.
|
|
542
|
+
|
|
543
|
+
async function generateScript(
|
|
544
|
+
outline: PodcastOutline,
|
|
545
|
+
chunks: DocumentChunk[],
|
|
546
|
+
keyPoints: KeyPoint[],
|
|
547
|
+
format: PodcastFormat,
|
|
548
|
+
speakers: Speaker[],
|
|
549
|
+
client: Anthropic
|
|
550
|
+
): Promise<ScriptSegment[]> {
|
|
551
|
+
const allSegments: ScriptSegment[] = [];
|
|
552
|
+
|
|
553
|
+
// Generate intro
|
|
554
|
+
const introSegments = await generateIntroSegments(
|
|
555
|
+
outline, speakers, format, client
|
|
556
|
+
);
|
|
557
|
+
allSegments.push(...introSegments);
|
|
558
|
+
|
|
559
|
+
// Generate each content segment independently (hallucination fix)
|
|
560
|
+
for (let i = 0; i < outline.segments.length; i++) {
|
|
561
|
+
const outlineSeg = outline.segments[i];
|
|
562
|
+
|
|
563
|
+
// Find relevant source chunks for this segment
|
|
564
|
+
const relevantChunks = findRelevantChunks(outlineSeg.keyPoints, chunks, keyPoints);
|
|
565
|
+
|
|
566
|
+
const segmentScript = await generateSegmentDialogue(
|
|
567
|
+
outlineSeg,
|
|
568
|
+
relevantChunks,
|
|
569
|
+
speakers,
|
|
570
|
+
format,
|
|
571
|
+
i === 0, // isFirstSegment
|
|
572
|
+
outline.segments[i - 1], // previousSegment (for transition)
|
|
573
|
+
client
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
allSegments.push(...segmentScript);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Generate outro
|
|
580
|
+
const outroSegments = await generateOutroSegments(
|
|
581
|
+
outline, speakers, format, client
|
|
582
|
+
);
|
|
583
|
+
allSegments.push(...outroSegments);
|
|
584
|
+
|
|
585
|
+
return allSegments;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function generateSegmentDialogue(
|
|
589
|
+
segment: OutlineSegment,
|
|
590
|
+
relevantChunks: DocumentChunk[],
|
|
591
|
+
speakers: Speaker[],
|
|
592
|
+
format: PodcastFormat,
|
|
593
|
+
isFirstSegment: boolean,
|
|
594
|
+
previousSegment: OutlineSegment | undefined,
|
|
595
|
+
client: Anthropic
|
|
596
|
+
): Promise<ScriptSegment[]> {
|
|
597
|
+
const host = speakers.find(s => s.role === 'host')!;
|
|
598
|
+
const guests = speakers.filter(s => s.role === 'guest');
|
|
599
|
+
|
|
600
|
+
// ── Step A: Host Agent generates questions ──
|
|
601
|
+
const hostResponse = await client.messages.create({
|
|
602
|
+
model: 'claude-sonnet-4-20250514',
|
|
603
|
+
max_tokens: 2048,
|
|
604
|
+
system: buildHostAgentSystem(host, format),
|
|
605
|
+
messages: [{
|
|
606
|
+
role: 'user',
|
|
607
|
+
content: `Generate the HOST's contributions for this podcast segment.
|
|
608
|
+
|
|
609
|
+
SEGMENT TOPIC: ${segment.topic}
|
|
610
|
+
KEY POINTS TO COVER: ${segment.keyPoints.join('; ')}
|
|
611
|
+
${previousSegment ? `TRANSITION FROM: ${previousSegment.topic}` : 'THIS IS THE FIRST CONTENT SEGMENT'}
|
|
612
|
+
TARGET DURATION: ${segment.estimatedDuration} seconds (~${Math.round(segment.estimatedDuration / 60 * 150)} words)
|
|
613
|
+
|
|
614
|
+
Return JSON array of host contributions:
|
|
615
|
+
[
|
|
616
|
+
{
|
|
617
|
+
"type": "transition|question|follow-up|reaction|summary",
|
|
618
|
+
"text": "What the host says",
|
|
619
|
+
"direction": "(tone/delivery direction)",
|
|
620
|
+
"targetGuest": "guest-id or null"
|
|
621
|
+
}
|
|
622
|
+
]
|
|
623
|
+
|
|
624
|
+
Generate 3-5 contributions that drive the conversation naturally.
|
|
625
|
+
Return ONLY valid JSON.`,
|
|
626
|
+
}],
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const hostContributions = JSON.parse(
|
|
630
|
+
(hostResponse.content[0] as { type: string; text: string }).text
|
|
631
|
+
.replace(/^```json\n?/, '').replace(/\n?```$/, '')
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
// ── Step B: Guest Agent generates responses ──
|
|
635
|
+
const sourceContext = relevantChunks.map(c => c.text).join('\n\n---\n\n');
|
|
636
|
+
|
|
637
|
+
const guestResponses: Array<{ guestId: string; responses: any[] }> = [];
|
|
638
|
+
|
|
639
|
+
for (const guest of guests) {
|
|
640
|
+
const guestResponse = await client.messages.create({
|
|
641
|
+
model: 'claude-sonnet-4-20250514',
|
|
642
|
+
max_tokens: 3072,
|
|
643
|
+
system: buildGuestAgentSystem(guest, format),
|
|
644
|
+
messages: [{
|
|
645
|
+
role: 'user',
|
|
646
|
+
content: `You are responding to the host's questions as a guest expert.
|
|
647
|
+
|
|
648
|
+
SOURCE MATERIAL (base ALL responses on this):
|
|
649
|
+
${sourceContext}
|
|
650
|
+
|
|
651
|
+
HOST'S QUESTIONS/PROMPTS:
|
|
652
|
+
${hostContributions.map((h: any, i: number) => `${i + 1}. [${h.type}] ${h.text}`).join('\n')}
|
|
653
|
+
|
|
654
|
+
SEGMENT TOPIC: ${segment.topic}
|
|
655
|
+
|
|
656
|
+
For each host contribution that is directed at you or is a general question, provide a response.
|
|
657
|
+
CRITICAL: Every factual claim MUST be supported by the source material above. If the source does not cover something, say "that's beyond what we're looking at today" rather than making something up.
|
|
658
|
+
|
|
659
|
+
Return JSON array:
|
|
660
|
+
[
|
|
661
|
+
{
|
|
662
|
+
"respondsTo": 1,
|
|
663
|
+
"text": "Guest's response",
|
|
664
|
+
"direction": "(tone/delivery direction)",
|
|
665
|
+
"sourceEvidence": "Brief quote or reference from source material that supports this"
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
|
|
669
|
+
Return ONLY valid JSON.`,
|
|
670
|
+
}],
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
const parsed = JSON.parse(
|
|
674
|
+
(guestResponse.content[0] as { type: string; text: string }).text
|
|
675
|
+
.replace(/^```json\n?/, '').replace(/\n?```$/, '')
|
|
676
|
+
);
|
|
677
|
+
guestResponses.push({ guestId: guest.id, responses: parsed });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ── Step C: Writer Agent composes final dialogue ──
|
|
681
|
+
const writerResponse = await client.messages.create({
|
|
682
|
+
model: 'claude-sonnet-4-20250514',
|
|
683
|
+
max_tokens: 4096,
|
|
684
|
+
system: buildWriterAgentSystem(format),
|
|
685
|
+
messages: [{
|
|
686
|
+
role: 'user',
|
|
687
|
+
content: `Compose the final podcast script segment from these raw dialogue elements.
|
|
688
|
+
|
|
689
|
+
HOST CONTRIBUTIONS:
|
|
690
|
+
${JSON.stringify(hostContributions, null, 2)}
|
|
691
|
+
|
|
692
|
+
GUEST RESPONSES:
|
|
693
|
+
${JSON.stringify(guestResponses, null, 2)}
|
|
694
|
+
|
|
695
|
+
SPEAKERS:
|
|
696
|
+
${speakers.map(s => `${s.id}: ${s.name} (${s.role}, ${s.voiceProfile.tone} tone)`).join('\n')}
|
|
697
|
+
|
|
698
|
+
RULES:
|
|
699
|
+
- Interleave host and guest naturally -- no long monologues
|
|
700
|
+
- Add brief reactions ("Exactly.", "That's fascinating.", "Right, and...")
|
|
701
|
+
- Include delivery directions in parentheses
|
|
702
|
+
- Keep each speaking turn to 2-4 sentences max
|
|
703
|
+
- Add natural pauses and thinking moments
|
|
704
|
+
- The segment should flow as real conversation, not a Q&A interview
|
|
705
|
+
${isFirstSegment ? '- Include the transition into this first topic' : `- Start with transition: ${segment.transitionFrom}`}
|
|
706
|
+
|
|
707
|
+
Return JSON array of ScriptSegment objects:
|
|
708
|
+
[
|
|
709
|
+
{
|
|
710
|
+
"speaker": "speaker-id",
|
|
711
|
+
"text": "What they say",
|
|
712
|
+
"direction": "(delivery direction)",
|
|
713
|
+
"timing": estimated_seconds,
|
|
714
|
+
"segmentType": "question|answer|transition|deep-dive|anecdote|summary",
|
|
715
|
+
"sourceReference": "chunk-id or null"
|
|
716
|
+
}
|
|
717
|
+
]
|
|
718
|
+
|
|
719
|
+
Return ONLY valid JSON.`,
|
|
720
|
+
}],
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const text = (writerResponse.content[0] as { type: string; text: string }).text;
|
|
724
|
+
return JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ─── Helper: Find Relevant Chunks ──────────────────────────────────────────
|
|
728
|
+
|
|
729
|
+
function findRelevantChunks(
|
|
730
|
+
segmentKeyPoints: string[],
|
|
731
|
+
allChunks: DocumentChunk[],
|
|
732
|
+
allKeyPoints: KeyPoint[]
|
|
733
|
+
): DocumentChunk[] {
|
|
734
|
+
// Find which KeyPoint objects match this segment's points
|
|
735
|
+
const matchingPoints = allKeyPoints.filter(kp =>
|
|
736
|
+
segmentKeyPoints.some(sp =>
|
|
737
|
+
sp.toLowerCase().includes(kp.point.toLowerCase().slice(0, 40)) ||
|
|
738
|
+
kp.point.toLowerCase().includes(sp.toLowerCase().slice(0, 40))
|
|
739
|
+
)
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
// Collect all referenced chunk IDs
|
|
743
|
+
const chunkIds = new Set<string>();
|
|
744
|
+
for (const point of matchingPoints) {
|
|
745
|
+
for (const id of point.sourceChunkIds) {
|
|
746
|
+
chunkIds.add(id);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Return matching chunks, or first 3 if no matches found
|
|
751
|
+
const matched = allChunks.filter(c => chunkIds.has(c.id));
|
|
752
|
+
return matched.length > 0 ? matched : allChunks.slice(0, 3);
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Step 6: Faithfulness Verification
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
// ─── Faithfulness Verification ──────────────────────────────────────────────
|
|
760
|
+
// CRITICAL STEP. Without this, scripts hallucinate facts not in the source.
|
|
761
|
+
|
|
762
|
+
async function verifyFaithfulness(
|
|
763
|
+
segments: ScriptSegment[],
|
|
764
|
+
sourceChunks: DocumentChunk[],
|
|
765
|
+
client: Anthropic
|
|
766
|
+
): Promise<FaithfulnessReport> {
|
|
767
|
+
const sourceText = sourceChunks.map(c => `[${c.id}] ${c.text}`).join('\n\n');
|
|
768
|
+
const segmentScores: FaithfulnessReport['segmentScores'] = [];
|
|
769
|
+
|
|
770
|
+
// Verify in batches of 5 segments
|
|
771
|
+
const batchSize = 5;
|
|
772
|
+
for (let i = 0; i < segments.length; i += batchSize) {
|
|
773
|
+
const batch = segments.slice(i, i + batchSize);
|
|
774
|
+
|
|
775
|
+
const response = await client.messages.create({
|
|
776
|
+
model: 'claude-sonnet-4-20250514',
|
|
777
|
+
max_tokens: 4096,
|
|
778
|
+
messages: [{
|
|
779
|
+
role: 'user',
|
|
780
|
+
content: `Verify the faithfulness of these podcast script segments against the source material.
|
|
781
|
+
|
|
782
|
+
SOURCE MATERIAL:
|
|
783
|
+
${sourceText}
|
|
784
|
+
|
|
785
|
+
SCRIPT SEGMENTS TO VERIFY:
|
|
786
|
+
${batch.map((seg, idx) => `[Segment ${i + idx}] ${seg.speaker}: ${seg.text}`).join('\n\n')}
|
|
787
|
+
|
|
788
|
+
For each segment, extract every factual claim and check if it is supported by the source material.
|
|
789
|
+
Opinions, questions, transitions, and conversational filler do NOT need source support.
|
|
790
|
+
|
|
791
|
+
Return JSON:
|
|
792
|
+
[
|
|
793
|
+
{
|
|
794
|
+
"segmentIndex": ${i},
|
|
795
|
+
"score": 0.95,
|
|
796
|
+
"claims": [
|
|
797
|
+
{
|
|
798
|
+
"claim": "the specific factual claim made",
|
|
799
|
+
"supported": true,
|
|
800
|
+
"sourceEvidence": "quote from source that supports this",
|
|
801
|
+
"correction": null
|
|
802
|
+
}
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
Scoring:
|
|
808
|
+
- 1.0 = all claims fully supported or segment is opinion/question/transition
|
|
809
|
+
- 0.7-0.99 = minor unsupported details
|
|
810
|
+
- Below 0.7 = contains fabricated facts -- MUST provide corrections
|
|
811
|
+
|
|
812
|
+
Return ONLY valid JSON.`,
|
|
813
|
+
}],
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
const text = (response.content[0] as { type: string; text: string }).text;
|
|
817
|
+
const parsed = JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
818
|
+
segmentScores.push(...parsed);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const overallScore = segmentScores.reduce((sum, s) => sum + s.score, 0) / segmentScores.length;
|
|
822
|
+
const flaggedSegments = segmentScores
|
|
823
|
+
.filter(s => s.score < 0.7)
|
|
824
|
+
.map(s => s.segmentIndex);
|
|
825
|
+
|
|
826
|
+
return { overallScore, segmentScores, flaggedSegments };
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## The Hallucination Chunking Fix
|
|
833
|
+
|
|
834
|
+
Based on "Hallucinate at the Last in Long Response Generation" (arXiv 2505.15291, May 2025).
|
|
835
|
+
|
|
836
|
+
### The Problem
|
|
837
|
+
|
|
838
|
+
When generating a full 30-60 minute podcast script in a single prompt, faithfulness **drops below 0.65 in the final third** of the output. The model starts confidently fabricating facts, statistics, and quotes that do not exist in the source material. This is not a context window issue -- it happens well within the model's capacity.
|
|
839
|
+
|
|
840
|
+
### The Pattern
|
|
841
|
+
|
|
842
|
+
```
|
|
843
|
+
Faithfulness Score vs. Position in Generated Script:
|
|
844
|
+
|
|
845
|
+
Segments 1-5: [||||||||||||||||||||] 0.95 (accurate)
|
|
846
|
+
Segments 6-10: [|||||||||||||||||| ] 0.88 (minor drift)
|
|
847
|
+
Segments 11-15: [|||||||||||||| ] 0.72 (noticeable fabrication)
|
|
848
|
+
Segments 16-20: [|||||||||| ] 0.58 (unreliable)
|
|
849
|
+
Segments 21+: [|||||||| ] 0.43 (hallucination-heavy)
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### The Fix: Per-Segment Generation with Verification
|
|
853
|
+
|
|
854
|
+
```
|
|
855
|
+
WRONG (single-shot):
|
|
856
|
+
All source text --> Single prompt --> Full 60-min script
|
|
857
|
+
Result: Final 1/3 is unfaithful
|
|
858
|
+
|
|
859
|
+
RIGHT (chunked):
|
|
860
|
+
Segment 1 outline + relevant chunks --> Generate --> Verify --> Accept/Fix
|
|
861
|
+
Segment 2 outline + relevant chunks --> Generate --> Verify --> Accept/Fix
|
|
862
|
+
Segment 3 outline + relevant chunks --> Generate --> Verify --> Accept/Fix
|
|
863
|
+
...
|
|
864
|
+
Concatenate verified segments --> Final script
|
|
865
|
+
Result: Consistent faithfulness throughout
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Implementation Rules
|
|
869
|
+
|
|
870
|
+
1. **Never feed the entire document as a single prompt** for scripts longer than 5 minutes
|
|
871
|
+
2. **Generate each segment independently** with only its relevant source chunks
|
|
872
|
+
3. **Verify faithfulness per-segment** before moving to the next
|
|
873
|
+
4. **Re-generate any segment** scoring below 0.7 with explicit instructions to stick to source
|
|
874
|
+
5. **Final pass:** concatenate all verified segments and smooth transitions
|
|
875
|
+
|
|
876
|
+
```typescript
|
|
877
|
+
// Anti-pattern: DO NOT do this for long scripts
|
|
878
|
+
const badScript = await generateEntireScript(fullDocument); // Hallucination risk!
|
|
879
|
+
|
|
880
|
+
// Correct pattern: generate and verify per-segment
|
|
881
|
+
const segments: ScriptSegment[] = [];
|
|
882
|
+
for (const outlineSegment of outline.segments) {
|
|
883
|
+
const relevantChunks = findRelevantChunks(outlineSegment.keyPoints, chunks, keyPoints);
|
|
884
|
+
let segmentScript = await generateSegmentDialogue(outlineSegment, relevantChunks, ...);
|
|
885
|
+
|
|
886
|
+
// Verify this segment
|
|
887
|
+
const report = await verifyFaithfulness(segmentScript, relevantChunks, client);
|
|
888
|
+
|
|
889
|
+
// Re-generate if unfaithful
|
|
890
|
+
if (report.overallScore < 0.7) {
|
|
891
|
+
const corrections = report.segmentScores
|
|
892
|
+
.flatMap(s => s.claims.filter(c => !c.supported).map(c => c.correction))
|
|
893
|
+
.filter(Boolean);
|
|
894
|
+
|
|
895
|
+
segmentScript = await regenerateWithCorrections(
|
|
896
|
+
outlineSegment, relevantChunks, corrections, ...
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
segments.push(...segmentScript);
|
|
901
|
+
}
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## Prompt Templates
|
|
907
|
+
|
|
908
|
+
### Host Agent System Prompt
|
|
909
|
+
|
|
910
|
+
```
|
|
911
|
+
Copy-pasteable prompt for Claude or Gemini:
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
You are a podcast HOST agent. Your job is to drive the conversation by:
|
|
916
|
+
|
|
917
|
+
1. Formulating clear, engaging questions that lead to insightful answers
|
|
918
|
+
2. Creating smooth transitions between topics
|
|
919
|
+
3. Reacting naturally to guest responses (brief reactions, not monologues)
|
|
920
|
+
4. Summarizing key takeaways at the end of each topic
|
|
921
|
+
5. Keeping the conversation on track and on time
|
|
922
|
+
|
|
923
|
+
VOICE PROFILE:
|
|
924
|
+
- Tone: {{tone}} (warm / curious / authoritative)
|
|
925
|
+
- Pace: {{pace}} (moderate -- adjust based on topic weight)
|
|
926
|
+
- Style: {{style}} (conversational -- this is a podcast, not a lecture)
|
|
927
|
+
|
|
928
|
+
RULES:
|
|
929
|
+
- Ask ONE question at a time (never double-barrel questions)
|
|
930
|
+
- Keep your turns to 1-2 sentences max (let guests talk)
|
|
931
|
+
- Use the guest's name occasionally for natural flow
|
|
932
|
+
- Signal topic transitions explicitly ("Let's shift to...", "Now I want to explore...")
|
|
933
|
+
- If a guest gives a vague answer, ask a specific follow-up
|
|
934
|
+
- Reference listener perspective ("Our listeners might be wondering...")
|
|
935
|
+
|
|
936
|
+
FORMAT: {{format}}
|
|
937
|
+
- two-speaker: Direct, intimate conversation with one guest
|
|
938
|
+
- panel: Moderate between 2-3 guests, ensure balanced airtime
|
|
939
|
+
- debate: Play devil's advocate, challenge both sides fairly
|
|
940
|
+
- solo-narration: N/A (no host in solo format)
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Guest Agent System Prompt
|
|
944
|
+
|
|
945
|
+
```
|
|
946
|
+
Copy-pasteable prompt for Claude or Gemini:
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
You are a podcast GUEST agent with expertise in the topics covered by the source documents provided.
|
|
951
|
+
|
|
952
|
+
YOUR PROFILE:
|
|
953
|
+
- Name: {{guest_name}}
|
|
954
|
+
- Expertise: {{expertise_area}}
|
|
955
|
+
- Speaking style: {{tone}} and {{style}}
|
|
956
|
+
|
|
957
|
+
SOURCE MATERIAL:
|
|
958
|
+
{{source_chunks}}
|
|
959
|
+
|
|
960
|
+
RULES:
|
|
961
|
+
- EVERY factual claim MUST be traceable to the source material above
|
|
962
|
+
- If the source does not cover a topic, say so honestly: "That's actually outside what this research covers, but..."
|
|
963
|
+
- Speak in natural, conversational language -- not academic prose
|
|
964
|
+
- Use concrete examples and analogies to explain complex points
|
|
965
|
+
- Keep responses to 3-5 sentences per turn (this is a conversation, not a lecture)
|
|
966
|
+
- Show genuine interest: "What's really interesting about this is..." / "The surprising thing is..."
|
|
967
|
+
- Cite specifics: "According to the data..." / "The research found that..."
|
|
968
|
+
- DO NOT invent statistics, quotes, or findings not in the source material
|
|
969
|
+
- When uncertain, qualify: "From what I've seen..." / "The evidence suggests..."
|
|
970
|
+
|
|
971
|
+
ANTI-HALLUCINATION GUARD:
|
|
972
|
+
Before each response, mentally check:
|
|
973
|
+
1. Is this claim in my source material? If no, do not state it as fact.
|
|
974
|
+
2. Am I adding nuance or inventing? Add nuance only from what the source implies.
|
|
975
|
+
3. Would a fact-checker find my source for this? If unsure, hedge or omit.
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### Writer Agent System Prompt
|
|
979
|
+
|
|
980
|
+
```
|
|
981
|
+
Copy-pasteable prompt for Claude or Gemini:
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
You are a podcast WRITER/PRODUCER agent. You take raw Host-Guest dialogue and produce a polished, natural-sounding podcast script.
|
|
986
|
+
|
|
987
|
+
YOUR JOB:
|
|
988
|
+
1. Interleave host and guest contributions into natural conversation flow
|
|
989
|
+
2. Add delivery directions (tone, pacing, emphasis) in parentheses
|
|
990
|
+
3. Insert natural conversational elements: brief reactions, thinking pauses, laughter cues
|
|
991
|
+
4. Ensure no speaker monologues -- max 4 sentences per turn
|
|
992
|
+
5. Add smooth transitions between topics
|
|
993
|
+
6. Control pacing: mix quick exchanges with deeper explorations
|
|
994
|
+
|
|
995
|
+
DELIVERY DIRECTIONS FORMAT:
|
|
996
|
+
- "(enthusiastically)" / "(thoughtfully)" / "(with emphasis)"
|
|
997
|
+
- "(leaning in)" / "(nodding)" / "(laughing)"
|
|
998
|
+
- "(pause)" / "(beat)" / "(takes a breath)"
|
|
999
|
+
- "(to [guest name])" / "(turning to the audience)"
|
|
1000
|
+
|
|
1001
|
+
NATURAL CONVERSATION ELEMENTS:
|
|
1002
|
+
- Brief reactions: "Right." / "Exactly." / "Hmm, interesting." / "Wow."
|
|
1003
|
+
- Thinking cues: "So what you're saying is..." / "Let me make sure I understand..."
|
|
1004
|
+
- Bridges: "And that connects to..." / "Which brings up something interesting..."
|
|
1005
|
+
- Engagement: "I love that example." / "That's a great point."
|
|
1006
|
+
|
|
1007
|
+
ANTI-PATTERN: AVOID
|
|
1008
|
+
- Long unbroken monologues (more than 4 sentences)
|
|
1009
|
+
- Every response starting with "Great question!" (synthetic enthusiasm)
|
|
1010
|
+
- Identical reaction patterns (vary reactions throughout)
|
|
1011
|
+
- Two speakers agreeing with everything (add nuance, pushback, different angles)
|
|
1012
|
+
|
|
1013
|
+
FORMAT GUIDELINES:
|
|
1014
|
+
- two-speaker: Intimate, flowing conversation
|
|
1015
|
+
- panel: Writer manages airtime balance, ensures each guest contributes
|
|
1016
|
+
- debate: Maintain tension, let disagreements breathe, host mediates
|
|
1017
|
+
- solo-narration: Single voice with rhetorical questions and storytelling pacing
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Faithfulness Checker Prompt
|
|
1021
|
+
|
|
1022
|
+
```
|
|
1023
|
+
Copy-pasteable prompt for Claude or Gemini:
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
You are a FAITHFULNESS VERIFICATION agent. Your job is to compare podcast script segments against source documents and identify any claims not supported by the source.
|
|
1028
|
+
|
|
1029
|
+
SOURCE DOCUMENTS:
|
|
1030
|
+
{{source_text}}
|
|
1031
|
+
|
|
1032
|
+
SCRIPT SEGMENTS TO VERIFY:
|
|
1033
|
+
{{script_segments}}
|
|
1034
|
+
|
|
1035
|
+
FOR EACH SEGMENT:
|
|
1036
|
+
1. Extract every factual claim (skip opinions, questions, transitions, filler)
|
|
1037
|
+
2. For each claim, search the source documents for supporting evidence
|
|
1038
|
+
3. Score the segment:
|
|
1039
|
+
- 1.0 = all claims supported or segment is non-factual (question, opinion, transition)
|
|
1040
|
+
- 0.8-0.99 = minor unsupported details that could be inferred
|
|
1041
|
+
- 0.5-0.79 = contains claims not in the source but plausible
|
|
1042
|
+
- Below 0.5 = contains fabricated information
|
|
1043
|
+
|
|
1044
|
+
WHAT COUNTS AS UNSUPPORTED:
|
|
1045
|
+
- Specific numbers or statistics not in the source
|
|
1046
|
+
- Quotes attributed to people not quoted in the source
|
|
1047
|
+
- Causal claims ("X caused Y") the source doesn't make
|
|
1048
|
+
- Specifics beyond what the source generalizes
|
|
1049
|
+
|
|
1050
|
+
WHAT DOES NOT NEED SOURCE SUPPORT:
|
|
1051
|
+
- Host questions
|
|
1052
|
+
- Conversational filler ("That's interesting", "Right")
|
|
1053
|
+
- Transitions and signposting
|
|
1054
|
+
- Opinions explicitly framed as opinions ("I think...", "In my view...")
|
|
1055
|
+
- General knowledge (widely known facts)
|
|
1056
|
+
|
|
1057
|
+
For any claim scoring below 0.7, provide a CORRECTION that stays faithful to the source.
|
|
1058
|
+
|
|
1059
|
+
Return structured JSON with scores and claim-level analysis.
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
---
|
|
1063
|
+
|
|
1064
|
+
## Script Formats
|
|
1065
|
+
|
|
1066
|
+
### Simple Two-Speaker (Host + Guest)
|
|
1067
|
+
|
|
1068
|
+
The classic interview/discussion format. Best for focused, deep explorations of a single topic.
|
|
1069
|
+
|
|
1070
|
+
```typescript
|
|
1071
|
+
function createTwoSpeakerConfig(): Speaker[] {
|
|
1072
|
+
return [
|
|
1073
|
+
{
|
|
1074
|
+
id: 'host',
|
|
1075
|
+
name: 'Alex',
|
|
1076
|
+
role: 'host',
|
|
1077
|
+
voiceProfile: {
|
|
1078
|
+
tone: 'curious',
|
|
1079
|
+
pace: 'moderate',
|
|
1080
|
+
style: 'conversational',
|
|
1081
|
+
},
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
id: 'guest-1',
|
|
1085
|
+
name: 'Dr. Morgan',
|
|
1086
|
+
role: 'guest',
|
|
1087
|
+
voiceProfile: {
|
|
1088
|
+
tone: 'authoritative',
|
|
1089
|
+
pace: 'moderate',
|
|
1090
|
+
style: 'conversational',
|
|
1091
|
+
},
|
|
1092
|
+
expertise: 'Domain expert based on source document',
|
|
1093
|
+
},
|
|
1094
|
+
];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Example output structure:
|
|
1098
|
+
const twoSpeakerExample: ScriptSegment[] = [
|
|
1099
|
+
{
|
|
1100
|
+
speaker: 'host',
|
|
1101
|
+
text: "Welcome to the show. Today we're diving into something that caught my eye in this research -- the idea that faithfulness in AI-generated content actually degrades the longer the output gets. That's pretty alarming if you think about it.",
|
|
1102
|
+
direction: '(warm, setting the stage)',
|
|
1103
|
+
timing: 12,
|
|
1104
|
+
segmentType: 'intro',
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
speaker: 'guest-1',
|
|
1108
|
+
text: "It really is. And the key finding here is that it's not just a gradual decline -- there's a sharp drop-off. The research shows faithfulness scores falling below 0.65 in the final segments of long-form generation.",
|
|
1109
|
+
direction: '(leaning in, emphasis on the numbers)',
|
|
1110
|
+
timing: 10,
|
|
1111
|
+
segmentType: 'answer',
|
|
1112
|
+
sourceReference: 'chunk-3',
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
speaker: 'host',
|
|
1116
|
+
text: "Below 0.65. So more than a third of what the AI says in the back half is essentially... made up?",
|
|
1117
|
+
direction: '(genuine surprise)',
|
|
1118
|
+
timing: 5,
|
|
1119
|
+
segmentType: 'question',
|
|
1120
|
+
},
|
|
1121
|
+
];
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### Panel Discussion (Host + 2-3 Guests)
|
|
1125
|
+
|
|
1126
|
+
Best for broad topics with multiple angles, or when the source material covers several distinct domains.
|
|
1127
|
+
|
|
1128
|
+
```typescript
|
|
1129
|
+
function createPanelConfig(): Speaker[] {
|
|
1130
|
+
return [
|
|
1131
|
+
{
|
|
1132
|
+
id: 'host',
|
|
1133
|
+
name: 'Alex',
|
|
1134
|
+
role: 'host',
|
|
1135
|
+
voiceProfile: { tone: 'warm', pace: 'moderate', style: 'conversational' },
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
id: 'guest-research',
|
|
1139
|
+
name: 'Dr. Taylor',
|
|
1140
|
+
role: 'guest',
|
|
1141
|
+
voiceProfile: { tone: 'authoritative', pace: 'slow', style: 'academic' },
|
|
1142
|
+
expertise: 'Research methodology and findings',
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
id: 'guest-practice',
|
|
1146
|
+
name: 'Jordan',
|
|
1147
|
+
role: 'guest',
|
|
1148
|
+
voiceProfile: { tone: 'energetic', pace: 'fast', style: 'conversational' },
|
|
1149
|
+
expertise: 'Practical applications and industry experience',
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
id: 'guest-critique',
|
|
1153
|
+
name: 'Sam',
|
|
1154
|
+
role: 'guest',
|
|
1155
|
+
voiceProfile: { tone: 'measured', pace: 'moderate', style: 'conversational' },
|
|
1156
|
+
expertise: 'Critical analysis and limitations',
|
|
1157
|
+
},
|
|
1158
|
+
];
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// Panel management: ensure balanced airtime
|
|
1162
|
+
function balanceSpeakerTime(segments: ScriptSegment[], speakers: Speaker[]): void {
|
|
1163
|
+
const timeBySpeaker = new Map<string, number>();
|
|
1164
|
+
for (const seg of segments) {
|
|
1165
|
+
const current = timeBySpeaker.get(seg.speaker) || 0;
|
|
1166
|
+
timeBySpeaker.set(seg.speaker, current + (seg.timing || 0));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Log imbalance warnings
|
|
1170
|
+
const guests = speakers.filter(s => s.role === 'guest');
|
|
1171
|
+
const avgGuestTime = guests.reduce(
|
|
1172
|
+
(sum, g) => sum + (timeBySpeaker.get(g.id) || 0), 0
|
|
1173
|
+
) / guests.length;
|
|
1174
|
+
|
|
1175
|
+
for (const guest of guests) {
|
|
1176
|
+
const guestTime = timeBySpeaker.get(guest.id) || 0;
|
|
1177
|
+
const ratio = guestTime / avgGuestTime;
|
|
1178
|
+
if (ratio < 0.6) {
|
|
1179
|
+
console.warn(`WARNING: ${guest.name} has only ${Math.round(ratio * 100)}% of average guest airtime`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
### Solo Narration (Single Speaker, Educational)
|
|
1186
|
+
|
|
1187
|
+
Best for short explainers, educational content, or when a conversational format would feel forced.
|
|
1188
|
+
|
|
1189
|
+
```typescript
|
|
1190
|
+
function createSoloConfig(): Speaker[] {
|
|
1191
|
+
return [
|
|
1192
|
+
{
|
|
1193
|
+
id: 'narrator',
|
|
1194
|
+
name: 'Narrator',
|
|
1195
|
+
role: 'narrator',
|
|
1196
|
+
voiceProfile: {
|
|
1197
|
+
tone: 'warm',
|
|
1198
|
+
pace: 'moderate',
|
|
1199
|
+
style: 'storytelling',
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
];
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Solo narration uses rhetorical questions instead of real dialogue
|
|
1206
|
+
const soloExample: ScriptSegment[] = [
|
|
1207
|
+
{
|
|
1208
|
+
speaker: 'narrator',
|
|
1209
|
+
text: "Have you ever wondered why AI-generated content seems to get less reliable the longer it goes? You're not imagining it.",
|
|
1210
|
+
direction: '(conversational, drawing the listener in)',
|
|
1211
|
+
timing: 7,
|
|
1212
|
+
segmentType: 'intro',
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
speaker: 'narrator',
|
|
1216
|
+
text: "Researchers found something startling. When they measured how faithful AI output stayed to source documents, the accuracy held steady for the first chunk. But then, almost like clockwork, it started to drift.",
|
|
1217
|
+
direction: '(measured, building tension)',
|
|
1218
|
+
timing: 12,
|
|
1219
|
+
segmentType: 'deep-dive',
|
|
1220
|
+
sourceReference: 'chunk-2',
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
speaker: 'narrator',
|
|
1224
|
+
text: "(pause) And by the final third of the output? Faithfulness dropped below sixty-five percent. The AI was essentially making things up -- confidently, fluently, but fabricating nonetheless.",
|
|
1225
|
+
direction: '(emphasis on the reveal, slower pace)',
|
|
1226
|
+
timing: 10,
|
|
1227
|
+
segmentType: 'deep-dive',
|
|
1228
|
+
sourceReference: 'chunk-3',
|
|
1229
|
+
},
|
|
1230
|
+
];
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
### Debate Format (Two Opposing Viewpoints)
|
|
1234
|
+
|
|
1235
|
+
Best for controversial topics, comparing competing approaches, or exploring trade-offs.
|
|
1236
|
+
|
|
1237
|
+
```typescript
|
|
1238
|
+
function createDebateConfig(): Speaker[] {
|
|
1239
|
+
return [
|
|
1240
|
+
{
|
|
1241
|
+
id: 'moderator',
|
|
1242
|
+
name: 'Alex',
|
|
1243
|
+
role: 'host',
|
|
1244
|
+
voiceProfile: { tone: 'measured', pace: 'moderate', style: 'formal' },
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
id: 'advocate',
|
|
1248
|
+
name: 'Dr. Chen',
|
|
1249
|
+
role: 'guest',
|
|
1250
|
+
voiceProfile: { tone: 'energetic', pace: 'fast', style: 'conversational' },
|
|
1251
|
+
expertise: 'Argues FOR the position supported by source material',
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
id: 'skeptic',
|
|
1255
|
+
name: 'Professor Williams',
|
|
1256
|
+
role: 'guest',
|
|
1257
|
+
voiceProfile: { tone: 'measured', pace: 'slow', style: 'academic' },
|
|
1258
|
+
expertise: 'Raises limitations, counter-arguments, and caveats from the source',
|
|
1259
|
+
},
|
|
1260
|
+
];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Debate format maps to NotebookLM's "critique" and "debate" options.
|
|
1264
|
+
// The moderator ensures both sides get fair airtime and steers
|
|
1265
|
+
// toward productive disagreement (not just contradiction).
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
---
|
|
1269
|
+
|
|
1270
|
+
## Anti-Pattern: Synthetic Intimacy
|
|
1271
|
+
|
|
1272
|
+
Based on arXiv 2511.08654 analysis of Google NotebookLM's podcast generation.
|
|
1273
|
+
|
|
1274
|
+
### The Problem
|
|
1275
|
+
|
|
1276
|
+
NotebookLM and similar tools default to a single template: two perky American English speakers with manufactured rapport, exclaiming "Oh wow!" and "That's so fascinating!" regardless of whether the source material is a cancer research paper or a tax code summary. This "synthetic intimacy" creates:
|
|
1277
|
+
|
|
1278
|
+
1. **Monoculture** -- Every podcast sounds identical regardless of content
|
|
1279
|
+
2. **Tonal mismatch** -- Enthusiastic delivery for serious/somber topics feels disrespectful
|
|
1280
|
+
3. **Fake rapport** -- AI speakers reference shared experiences they never had
|
|
1281
|
+
4. **Engagement ceiling** -- Listeners disengage when the tone never varies
|
|
1282
|
+
|
|
1283
|
+
### The Fix: Content-Driven Tone
|
|
1284
|
+
|
|
1285
|
+
```typescript
|
|
1286
|
+
// ─── Tone Selection Based on Content Analysis ───────────────────────────────
|
|
1287
|
+
|
|
1288
|
+
type ContentTone = 'serious' | 'neutral' | 'inspiring' | 'technical' | 'controversial';
|
|
1289
|
+
|
|
1290
|
+
function selectTone(keyPoints: KeyPoint[], documentTitle: string): ContentTone {
|
|
1291
|
+
// Analyze content to determine appropriate tone
|
|
1292
|
+
const seriousSignals = ['death', 'crisis', 'failure', 'loss', 'disease', 'poverty', 'war'];
|
|
1293
|
+
const inspiringSignals = ['breakthrough', 'success', 'innovation', 'hope', 'growth', 'overcome'];
|
|
1294
|
+
const technicalSignals = ['algorithm', 'implementation', 'architecture', 'protocol', 'specification'];
|
|
1295
|
+
const controversialSignals = ['debate', 'criticism', 'versus', 'disagree', 'alternative', 'challenge'];
|
|
1296
|
+
|
|
1297
|
+
const allText = keyPoints.map(kp => kp.point).join(' ').toLowerCase();
|
|
1298
|
+
|
|
1299
|
+
if (seriousSignals.some(s => allText.includes(s))) return 'serious';
|
|
1300
|
+
if (controversialSignals.some(s => allText.includes(s))) return 'controversial';
|
|
1301
|
+
if (technicalSignals.some(s => allText.includes(s))) return 'technical';
|
|
1302
|
+
if (inspiringSignals.some(s => allText.includes(s))) return 'inspiring';
|
|
1303
|
+
return 'neutral';
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function adjustVoiceProfiles(speakers: Speaker[], tone: ContentTone): Speaker[] {
|
|
1307
|
+
const toneProfiles: Record<ContentTone, Partial<VoiceProfile>> = {
|
|
1308
|
+
serious: { tone: 'measured', pace: 'slow', style: 'formal' },
|
|
1309
|
+
neutral: { tone: 'warm', pace: 'moderate', style: 'conversational' },
|
|
1310
|
+
inspiring: { tone: 'warm', pace: 'moderate', style: 'storytelling' },
|
|
1311
|
+
technical: { tone: 'authoritative', pace: 'moderate', style: 'academic' },
|
|
1312
|
+
controversial: { tone: 'curious', pace: 'moderate', style: 'conversational' },
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
const profile = toneProfiles[tone];
|
|
1316
|
+
|
|
1317
|
+
return speakers.map(s => ({
|
|
1318
|
+
...s,
|
|
1319
|
+
voiceProfile: { ...s.voiceProfile, ...profile },
|
|
1320
|
+
}));
|
|
1321
|
+
}
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
### Specific Anti-Patterns to Avoid
|
|
1325
|
+
|
|
1326
|
+
| Anti-Pattern | Example | Fix |
|
|
1327
|
+
|---|---|---|
|
|
1328
|
+
| Forced enthusiasm | "Oh WOW, this is AMAZING!" | "That's a significant finding." |
|
|
1329
|
+
| Template reactions | Every segment starts with "Great question!" | Vary reactions: "Hmm.", "Right.", "I'd push back on that." |
|
|
1330
|
+
| Fake shared history | "Remember when we talked about..." | Cut references to non-existent shared experiences |
|
|
1331
|
+
| Identical energy | Same excitement level for 60 minutes | Match energy to content: somber for heavy topics, animated for breakthroughs |
|
|
1332
|
+
| American monoculture | Always casual American English | Adjust register to audience: academic, professional, casual |
|
|
1333
|
+
| Agreement loop | Both speakers always agree | Build in genuine questions, pushback, "devil's advocate" moments |
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## Duration Control
|
|
1338
|
+
|
|
1339
|
+
### Word Count to Duration Mapping
|
|
1340
|
+
|
|
1341
|
+
```
|
|
1342
|
+
Speaking rate: ~150 words per minute (conversational podcast pace)
|
|
1343
|
+
|
|
1344
|
+
Duration Words Key Points Segments Best For
|
|
1345
|
+
──────── ────── ────────── ──────── ────────────────────────
|
|
1346
|
+
5 min ~750 3-4 3 Quick summary, teaser
|
|
1347
|
+
15 min ~2,250 6-8 5 Article deep-dive
|
|
1348
|
+
30 min ~4,500 10-15 8 Research paper, report
|
|
1349
|
+
60 min ~9,000 20+ 12 Full document coverage
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
### Implementation
|
|
1353
|
+
|
|
1354
|
+
```typescript
|
|
1355
|
+
// ─── Duration Calculator ────────────────────────────────────────────────────
|
|
1356
|
+
|
|
1357
|
+
interface DurationConfig {
|
|
1358
|
+
targetDuration: '5min' | '15min' | '30min' | '60min';
|
|
1359
|
+
wordsPerMinute: number; // Default: 150 for conversational pace
|
|
1360
|
+
introOutroSeconds: number; // Default: 60 (30s intro + 30s outro)
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function calculateBudget(config: DurationConfig): {
|
|
1364
|
+
totalWords: number;
|
|
1365
|
+
contentWords: number;
|
|
1366
|
+
keyPointTarget: number;
|
|
1367
|
+
segmentCount: number;
|
|
1368
|
+
wordsPerSegment: number;
|
|
1369
|
+
} {
|
|
1370
|
+
const durationSeconds: Record<string, number> = {
|
|
1371
|
+
'5min': 300,
|
|
1372
|
+
'15min': 900,
|
|
1373
|
+
'30min': 1800,
|
|
1374
|
+
'60min': 3600,
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
const totalSeconds = durationSeconds[config.targetDuration] || 900;
|
|
1378
|
+
const contentSeconds = totalSeconds - config.introOutroSeconds;
|
|
1379
|
+
const contentWords = Math.round((contentSeconds / 60) * config.wordsPerMinute);
|
|
1380
|
+
const totalWords = Math.round((totalSeconds / 60) * config.wordsPerMinute);
|
|
1381
|
+
|
|
1382
|
+
// Heuristic: ~300 words per key point discussion
|
|
1383
|
+
const keyPointTarget = Math.round(contentWords / 300);
|
|
1384
|
+
|
|
1385
|
+
// Heuristic: ~3-5 minutes per segment
|
|
1386
|
+
const segmentCount = Math.max(3, Math.round(contentSeconds / 180));
|
|
1387
|
+
const wordsPerSegment = Math.round(contentWords / segmentCount);
|
|
1388
|
+
|
|
1389
|
+
return { totalWords, contentWords, keyPointTarget, segmentCount, wordsPerSegment };
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Usage:
|
|
1393
|
+
// const budget = calculateBudget({
|
|
1394
|
+
// targetDuration: '30min',
|
|
1395
|
+
// wordsPerMinute: 150,
|
|
1396
|
+
// introOutroSeconds: 60,
|
|
1397
|
+
// });
|
|
1398
|
+
// => { totalWords: 4500, contentWords: 4350, keyPointTarget: 14, segmentCount: 8, wordsPerSegment: 543 }
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
### Enforcement During Generation
|
|
1402
|
+
|
|
1403
|
+
```typescript
|
|
1404
|
+
function enforceWordBudget(segments: ScriptSegment[], maxWords: number): ScriptSegment[] {
|
|
1405
|
+
let totalWords = 0;
|
|
1406
|
+
|
|
1407
|
+
return segments.filter(seg => {
|
|
1408
|
+
const segWords = seg.text.split(/\s+/).length;
|
|
1409
|
+
if (totalWords + segWords > maxWords) {
|
|
1410
|
+
return false; // Drop segments that exceed budget
|
|
1411
|
+
}
|
|
1412
|
+
totalWords += segWords;
|
|
1413
|
+
return true;
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
## Agent System Prompt Builders
|
|
1421
|
+
|
|
1422
|
+
```typescript
|
|
1423
|
+
// ─── System Prompt Builders ─────────────────────────────────────────────────
|
|
1424
|
+
|
|
1425
|
+
function buildHostAgentSystem(host: Speaker, format: PodcastFormat): string {
|
|
1426
|
+
return `You are ${host.name}, a podcast host.
|
|
1427
|
+
|
|
1428
|
+
VOICE: ${host.voiceProfile.tone} tone, ${host.voiceProfile.pace} pace, ${host.voiceProfile.style} style.
|
|
1429
|
+
|
|
1430
|
+
YOUR ROLE:
|
|
1431
|
+
- Drive the conversation with clear, engaging questions
|
|
1432
|
+
- Create smooth transitions between topics
|
|
1433
|
+
- React naturally (brief reactions, not monologues)
|
|
1434
|
+
- Keep turns to 1-2 sentences
|
|
1435
|
+
- Reference listeners: "Our listeners might wonder..."
|
|
1436
|
+
|
|
1437
|
+
FORMAT: ${format}
|
|
1438
|
+
${format === 'panel' ? '- Ensure balanced airtime between all guests' : ''}
|
|
1439
|
+
${format === 'debate' ? '- Play fair moderator, challenge both sides' : ''}
|
|
1440
|
+
|
|
1441
|
+
ANTI-PATTERNS TO AVOID:
|
|
1442
|
+
- "Great question!" (never say this)
|
|
1443
|
+
- Double-barrel questions (one question at a time)
|
|
1444
|
+
- Long introductions before asking the actual question
|
|
1445
|
+
- Answering your own question`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function buildGuestAgentSystem(guest: Speaker, format: PodcastFormat): string {
|
|
1449
|
+
return `You are ${guest.name}, a podcast guest expert.
|
|
1450
|
+
|
|
1451
|
+
EXPERTISE: ${guest.expertise || 'General domain expert'}
|
|
1452
|
+
VOICE: ${guest.voiceProfile.tone} tone, ${guest.voiceProfile.pace} pace, ${guest.voiceProfile.style} style.
|
|
1453
|
+
|
|
1454
|
+
YOUR ROLE:
|
|
1455
|
+
- Provide domain expertise grounded in source documents
|
|
1456
|
+
- Use concrete examples and analogies
|
|
1457
|
+
- Keep responses to 3-5 sentences per turn
|
|
1458
|
+
- Cite specifics: "The data shows..." / "According to..."
|
|
1459
|
+
|
|
1460
|
+
FAITHFULNESS RULES:
|
|
1461
|
+
- EVERY factual claim must be traceable to your source material
|
|
1462
|
+
- If unsure, hedge: "From what I've seen..." / "The evidence suggests..."
|
|
1463
|
+
- If the source doesn't cover it, say so: "That's beyond what this research covers"
|
|
1464
|
+
- NEVER invent statistics, quotes, or findings
|
|
1465
|
+
|
|
1466
|
+
${format === 'debate' ? '- You may disagree with other guests -- support your position with evidence' : ''}`;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function buildWriterAgentSystem(format: PodcastFormat): string {
|
|
1470
|
+
return `You are a podcast WRITER/PRODUCER.
|
|
1471
|
+
|
|
1472
|
+
YOUR JOB: Take raw Host-Guest dialogue and produce polished, natural-sounding script.
|
|
1473
|
+
|
|
1474
|
+
RULES:
|
|
1475
|
+
1. Interleave speakers naturally -- no long monologues (max 4 sentences per turn)
|
|
1476
|
+
2. Add delivery directions in parentheses: (enthusiastically), (pause), (thoughtfully)
|
|
1477
|
+
3. Insert natural reactions: "Right.", "Exactly.", "Hmm.", "That's a key point."
|
|
1478
|
+
4. Vary reactions throughout -- never repeat the same reaction twice in a row
|
|
1479
|
+
5. Include thinking cues: "So what you're saying is...", "Let me make sure I understand..."
|
|
1480
|
+
6. Add bridges: "And that connects to...", "Which brings up..."
|
|
1481
|
+
|
|
1482
|
+
FORMAT: ${format}
|
|
1483
|
+
${format === 'panel' ? '- Balance airtime between guests; host mediates' : ''}
|
|
1484
|
+
${format === 'debate' ? '- Let disagreements breathe; host mediates; maintain productive tension' : ''}
|
|
1485
|
+
${format === 'solo-narration' ? '- Use rhetorical questions and storytelling rhythm' : ''}
|
|
1486
|
+
|
|
1487
|
+
ANTI-PATTERNS:
|
|
1488
|
+
- Synthetic enthusiasm (don't force excitement)
|
|
1489
|
+
- Agreement loops (build in pushback and questions)
|
|
1490
|
+
- Template phrases (vary language throughout)
|
|
1491
|
+
- Monotonous pacing (mix quick exchanges with deeper explorations)`;
|
|
1492
|
+
}
|
|
1493
|
+
```
|
|
1494
|
+
|
|
1495
|
+
---
|
|
1496
|
+
|
|
1497
|
+
## Intro and Outro Generation
|
|
1498
|
+
|
|
1499
|
+
```typescript
|
|
1500
|
+
// ─── Intro/Outro Generators ─────────────────────────────────────────────────
|
|
1501
|
+
|
|
1502
|
+
async function generateIntroSegments(
|
|
1503
|
+
outline: PodcastOutline,
|
|
1504
|
+
speakers: Speaker[],
|
|
1505
|
+
format: PodcastFormat,
|
|
1506
|
+
client: Anthropic
|
|
1507
|
+
): Promise<ScriptSegment[]> {
|
|
1508
|
+
const host = speakers.find(s => s.role === 'host' || s.role === 'narrator')!;
|
|
1509
|
+
|
|
1510
|
+
const response = await client.messages.create({
|
|
1511
|
+
model: 'claude-sonnet-4-20250514',
|
|
1512
|
+
max_tokens: 1024,
|
|
1513
|
+
messages: [{
|
|
1514
|
+
role: 'user',
|
|
1515
|
+
content: `Write the opening 30 seconds of a ${format} podcast episode.
|
|
1516
|
+
|
|
1517
|
+
TITLE: ${outline.title}
|
|
1518
|
+
HOOK: ${outline.hook}
|
|
1519
|
+
HOST: ${host.name}
|
|
1520
|
+
GUESTS: ${speakers.filter(s => s.role === 'guest').map(s => `${s.name} (${s.expertise})`).join(', ') || 'N/A'}
|
|
1521
|
+
|
|
1522
|
+
The intro should:
|
|
1523
|
+
1. Start with the hook (grab attention immediately)
|
|
1524
|
+
2. Briefly introduce the topic
|
|
1525
|
+
3. Introduce guests (if any)
|
|
1526
|
+
4. Tease what listeners will learn
|
|
1527
|
+
|
|
1528
|
+
Keep it under 100 words total. Natural, not scripted-sounding.
|
|
1529
|
+
|
|
1530
|
+
Return JSON array of ScriptSegment objects.
|
|
1531
|
+
Return ONLY valid JSON.`,
|
|
1532
|
+
}],
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
const text = (response.content[0] as { type: string; text: string }).text;
|
|
1536
|
+
return JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
async function generateOutroSegments(
|
|
1540
|
+
outline: PodcastOutline,
|
|
1541
|
+
speakers: Speaker[],
|
|
1542
|
+
format: PodcastFormat,
|
|
1543
|
+
client: Anthropic
|
|
1544
|
+
): Promise<ScriptSegment[]> {
|
|
1545
|
+
const host = speakers.find(s => s.role === 'host' || s.role === 'narrator')!;
|
|
1546
|
+
|
|
1547
|
+
const response = await client.messages.create({
|
|
1548
|
+
model: 'claude-sonnet-4-20250514',
|
|
1549
|
+
max_tokens: 1024,
|
|
1550
|
+
messages: [{
|
|
1551
|
+
role: 'user',
|
|
1552
|
+
content: `Write the closing 30 seconds of a ${format} podcast episode.
|
|
1553
|
+
|
|
1554
|
+
TITLE: ${outline.title}
|
|
1555
|
+
CLOSING THOUGHT: ${outline.closingThought}
|
|
1556
|
+
HOST: ${host.name}
|
|
1557
|
+
|
|
1558
|
+
The outro should:
|
|
1559
|
+
1. Summarize the single most important takeaway
|
|
1560
|
+
2. Include the closing thought
|
|
1561
|
+
3. Thank guests (if any)
|
|
1562
|
+
4. End with a forward-looking statement or call to action
|
|
1563
|
+
|
|
1564
|
+
Keep it under 80 words. Leave listeners thinking, not checking out.
|
|
1565
|
+
|
|
1566
|
+
Return JSON array of ScriptSegment objects.
|
|
1567
|
+
Return ONLY valid JSON.`,
|
|
1568
|
+
}],
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
const text = (response.content[0] as { type: string; text: string }).text;
|
|
1572
|
+
return JSON.parse(text.replace(/^```json\n?/, '').replace(/\n?```$/, ''));
|
|
1573
|
+
}
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
---
|
|
1577
|
+
|
|
1578
|
+
## Full Pipeline Orchestrator
|
|
1579
|
+
|
|
1580
|
+
```typescript
|
|
1581
|
+
// ─── Full Pipeline ──────────────────────────────────────────────────────────
|
|
1582
|
+
|
|
1583
|
+
interface PipelineConfig {
|
|
1584
|
+
inputPath: string; // Path to document (PDF, TXT, MD, HTML)
|
|
1585
|
+
duration: '5min' | '15min' | '30min' | '60min';
|
|
1586
|
+
format: PodcastFormat;
|
|
1587
|
+
speakers?: Speaker[]; // Custom speakers (optional, uses defaults)
|
|
1588
|
+
aiProvider: 'claude' | 'gemini';
|
|
1589
|
+
outputPath?: string; // Where to write the JSON script
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
async function generatePodcastScript(config: PipelineConfig): Promise<PodcastScript> {
|
|
1593
|
+
const client = new Anthropic();
|
|
1594
|
+
|
|
1595
|
+
// Step 1: Parse document
|
|
1596
|
+
console.log('Step 1/6: Parsing document...');
|
|
1597
|
+
const document = await parseDocument(config.inputPath);
|
|
1598
|
+
console.log(` Parsed: ${document.title} (${document.metadata.wordCount} words)`);
|
|
1599
|
+
|
|
1600
|
+
// Step 2: Chunk semantically
|
|
1601
|
+
console.log('Step 2/6: Chunking document...');
|
|
1602
|
+
const chunks = semanticChunk(document);
|
|
1603
|
+
console.log(` Created ${chunks.length} semantic chunks`);
|
|
1604
|
+
|
|
1605
|
+
// Step 3: Extract key points
|
|
1606
|
+
const budget = calculateBudget({
|
|
1607
|
+
targetDuration: config.duration,
|
|
1608
|
+
wordsPerMinute: 150,
|
|
1609
|
+
introOutroSeconds: 60,
|
|
1610
|
+
});
|
|
1611
|
+
console.log(`Step 3/6: Extracting ${budget.keyPointTarget} key points...`);
|
|
1612
|
+
const keyPoints = await extractKeyPoints(chunks, budget.keyPointTarget, client);
|
|
1613
|
+
console.log(` Extracted ${keyPoints.length} key points`);
|
|
1614
|
+
|
|
1615
|
+
// Step 4: Generate outline
|
|
1616
|
+
console.log('Step 4/6: Generating outline...');
|
|
1617
|
+
const outline = await generateOutline(keyPoints, config.duration, config.format, client);
|
|
1618
|
+
console.log(` Outline: ${outline.segments.length} segments`);
|
|
1619
|
+
|
|
1620
|
+
// Step 5: Multi-agent script generation (per-segment)
|
|
1621
|
+
console.log('Step 5/6: Generating script (multi-agent, per-segment)...');
|
|
1622
|
+
const speakers = config.speakers || getDefaultSpeakers(config.format);
|
|
1623
|
+
const contentTone = selectTone(keyPoints, document.title);
|
|
1624
|
+
const adjustedSpeakers = adjustVoiceProfiles(speakers, contentTone);
|
|
1625
|
+
|
|
1626
|
+
const segments = await generateScript(
|
|
1627
|
+
outline, chunks, keyPoints, config.format, adjustedSpeakers, client
|
|
1628
|
+
);
|
|
1629
|
+
console.log(` Generated ${segments.length} script segments`);
|
|
1630
|
+
|
|
1631
|
+
// Step 6: Faithfulness verification
|
|
1632
|
+
console.log('Step 6/6: Verifying faithfulness...');
|
|
1633
|
+
const report = await verifyFaithfulness(segments, chunks, client);
|
|
1634
|
+
console.log(` Faithfulness score: ${(report.overallScore * 100).toFixed(1)}%`);
|
|
1635
|
+
|
|
1636
|
+
if (report.flaggedSegments.length > 0) {
|
|
1637
|
+
console.warn(` WARNING: ${report.flaggedSegments.length} segments flagged for low faithfulness`);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Assemble final script
|
|
1641
|
+
const wordCount = segments.reduce((sum, s) => sum + s.text.split(/\s+/).length, 0);
|
|
1642
|
+
const estimatedDuration = Math.round((wordCount / 150) * 60);
|
|
1643
|
+
|
|
1644
|
+
const script: PodcastScript = {
|
|
1645
|
+
title: outline.title,
|
|
1646
|
+
duration: config.duration,
|
|
1647
|
+
speakers: adjustedSpeakers,
|
|
1648
|
+
segments,
|
|
1649
|
+
metadata: {
|
|
1650
|
+
sourceDocuments: [config.inputPath],
|
|
1651
|
+
generatedAt: new Date().toISOString(),
|
|
1652
|
+
wordCount,
|
|
1653
|
+
estimatedDuration,
|
|
1654
|
+
format: config.format,
|
|
1655
|
+
faithfulnessScore: report.overallScore,
|
|
1656
|
+
keyTopics: keyPoints.slice(0, 5).map(kp => kp.point),
|
|
1657
|
+
},
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
// Write output
|
|
1661
|
+
if (config.outputPath) {
|
|
1662
|
+
const { promises: fs } = await import('fs');
|
|
1663
|
+
await fs.writeFile(config.outputPath, JSON.stringify(script, null, 2), 'utf-8');
|
|
1664
|
+
console.log(`\nScript saved to: ${config.outputPath}`);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
console.log(`\nDone! ${wordCount} words, ~${Math.round(estimatedDuration / 60)} min`);
|
|
1668
|
+
console.log(`Faithfulness: ${(report.overallScore * 100).toFixed(1)}%`);
|
|
1669
|
+
|
|
1670
|
+
return script;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// ─── Default Speaker Configurations ─────────────────────────────────────────
|
|
1674
|
+
|
|
1675
|
+
function getDefaultSpeakers(format: PodcastFormat): Speaker[] {
|
|
1676
|
+
switch (format) {
|
|
1677
|
+
case 'two-speaker':
|
|
1678
|
+
return createTwoSpeakerConfig();
|
|
1679
|
+
case 'panel':
|
|
1680
|
+
return createPanelConfig();
|
|
1681
|
+
case 'solo-narration':
|
|
1682
|
+
return createSoloConfig();
|
|
1683
|
+
case 'debate':
|
|
1684
|
+
return createDebateConfig();
|
|
1685
|
+
default:
|
|
1686
|
+
return createTwoSpeakerConfig();
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
---
|
|
1692
|
+
|
|
1693
|
+
## Integration Points
|
|
1694
|
+
|
|
1695
|
+
### Input Sources
|
|
1696
|
+
|
|
1697
|
+
| Source | Parser | Notes |
|
|
1698
|
+
|---|---|---|
|
|
1699
|
+
| Raw text / Markdown | Built-in `parseText()` | Preserves heading structure |
|
|
1700
|
+
| PDF | `pdf-parse` npm package | Extracts text, page count |
|
|
1701
|
+
| HTML / URL | `cheerio` + fetch | Strips nav, scripts, styles |
|
|
1702
|
+
| YouTube transcript | `creative-multimedia/transcription-pipeline-selector.md` | Use Deepgram or Whisper on downloaded audio |
|
|
1703
|
+
| DOCX | `mammoth` npm package | Add parser similar to PDF |
|
|
1704
|
+
|
|
1705
|
+
### Output Targets
|
|
1706
|
+
|
|
1707
|
+
| Target | Format | Notes |
|
|
1708
|
+
|---|---|---|
|
|
1709
|
+
| TTS pipeline | Structured JSON (`PodcastScript`) | Pass segments with speaker IDs to TTS |
|
|
1710
|
+
| Human review | Formatted text with directions | Export as readable script document |
|
|
1711
|
+
| Audio production | SRT + script | For DAW or automated audio assembly |
|
|
1712
|
+
| Web player | JSON + audio chunks | Segment-level playback control |
|
|
1713
|
+
|
|
1714
|
+
### Related Skills
|
|
1715
|
+
|
|
1716
|
+
- `creative-multimedia/transcription-pipeline-selector.md` -- For audio input (YouTube, recorded audio)
|
|
1717
|
+
- `creative-multimedia/content-repurposing-pipeline.md` -- For repurposing generated podcast into clips, quote cards, social posts
|
|
1718
|
+
- `creative-multimedia/ffmpeg-command-generator.md` -- For audio processing and assembly
|
|
1719
|
+
- `creative-multimedia/audio-enhancement-pipeline.md` -- For post-processing TTS output
|
|
1720
|
+
|
|
1721
|
+
### TTS Output Bridge
|
|
1722
|
+
|
|
1723
|
+
```typescript
|
|
1724
|
+
// Convert PodcastScript to TTS-ready format
|
|
1725
|
+
interface TTSRequest {
|
|
1726
|
+
text: string;
|
|
1727
|
+
voiceId: string;
|
|
1728
|
+
speed: number; // 0.5 to 2.0
|
|
1729
|
+
outputPath: string;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function scriptToTTSRequests(
|
|
1733
|
+
script: PodcastScript,
|
|
1734
|
+
voiceMap: Record<string, string>, // speaker ID -> TTS voice ID
|
|
1735
|
+
outputDir: string
|
|
1736
|
+
): TTSRequest[] {
|
|
1737
|
+
return script.segments.map((seg, i) => ({
|
|
1738
|
+
text: seg.text,
|
|
1739
|
+
voiceId: voiceMap[seg.speaker] || 'default',
|
|
1740
|
+
speed: seg.direction?.includes('slow') ? 0.9
|
|
1741
|
+
: seg.direction?.includes('fast') ? 1.1
|
|
1742
|
+
: 1.0,
|
|
1743
|
+
outputPath: `${outputDir}/segment_${String(i).padStart(3, '0')}_${seg.speaker}.mp3`,
|
|
1744
|
+
}));
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// After TTS generation, concatenate with FFmpeg:
|
|
1748
|
+
// ffmpeg -f concat -safe 0 -i segments.txt -c:a aac -b:a 192k podcast.m4a
|
|
1749
|
+
```
|
|
1750
|
+
|
|
1751
|
+
---
|
|
1752
|
+
|
|
1753
|
+
## Dependencies
|
|
1754
|
+
|
|
1755
|
+
```json
|
|
1756
|
+
{
|
|
1757
|
+
"dependencies": {
|
|
1758
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
1759
|
+
"@google/generative-ai": "^0.21.0",
|
|
1760
|
+
"pdf-parse": "^1.1.1",
|
|
1761
|
+
"cheerio": "^1.0.0"
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
```bash
|
|
1767
|
+
npm install @anthropic-ai/sdk @google/generative-ai pdf-parse cheerio
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
---
|
|
1771
|
+
|
|
1772
|
+
## Usage Example
|
|
1773
|
+
|
|
1774
|
+
```typescript
|
|
1775
|
+
// ─── Quick Start ────────────────────────────────────────────────────────────
|
|
1776
|
+
|
|
1777
|
+
import { generatePodcastScript } from './podcast-script-generation';
|
|
1778
|
+
|
|
1779
|
+
// Generate a 15-minute two-speaker podcast from a research paper
|
|
1780
|
+
const script = await generatePodcastScript({
|
|
1781
|
+
inputPath: './papers/hallucination-research.pdf',
|
|
1782
|
+
duration: '15min',
|
|
1783
|
+
format: 'two-speaker',
|
|
1784
|
+
aiProvider: 'claude',
|
|
1785
|
+
outputPath: './output/podcast-script.json',
|
|
1786
|
+
});
|
|
1787
|
+
|
|
1788
|
+
console.log(`Generated: ${script.title}`);
|
|
1789
|
+
console.log(`Duration: ~${Math.round(script.metadata.estimatedDuration / 60)} min`);
|
|
1790
|
+
console.log(`Faithfulness: ${(script.metadata.faithfulnessScore * 100).toFixed(1)}%`);
|
|
1791
|
+
console.log(`Segments: ${script.segments.length}`);
|
|
1792
|
+
|
|
1793
|
+
// ─── With Custom Speakers ───────────────────────────────────────────────────
|
|
1794
|
+
|
|
1795
|
+
const customScript = await generatePodcastScript({
|
|
1796
|
+
inputPath: './docs/quarterly-report.pdf',
|
|
1797
|
+
duration: '30min',
|
|
1798
|
+
format: 'panel',
|
|
1799
|
+
aiProvider: 'claude',
|
|
1800
|
+
speakers: [
|
|
1801
|
+
{
|
|
1802
|
+
id: 'host',
|
|
1803
|
+
name: 'Thierry',
|
|
1804
|
+
role: 'host',
|
|
1805
|
+
voiceProfile: { tone: 'warm', pace: 'moderate', style: 'conversational' },
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
id: 'analyst',
|
|
1809
|
+
name: 'Financial Analyst',
|
|
1810
|
+
role: 'guest',
|
|
1811
|
+
voiceProfile: { tone: 'authoritative', pace: 'moderate', style: 'formal' },
|
|
1812
|
+
expertise: 'Financial data and market trends',
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
id: 'strategist',
|
|
1816
|
+
name: 'Strategy Director',
|
|
1817
|
+
role: 'guest',
|
|
1818
|
+
voiceProfile: { tone: 'energetic', pace: 'fast', style: 'conversational' },
|
|
1819
|
+
expertise: 'Business strategy and growth opportunities',
|
|
1820
|
+
},
|
|
1821
|
+
],
|
|
1822
|
+
outputPath: './output/quarterly-podcast.json',
|
|
1823
|
+
});
|
|
1824
|
+
```
|
|
1825
|
+
|
|
1826
|
+
---
|
|
1827
|
+
|
|
1828
|
+
## Common Pitfalls
|
|
1829
|
+
|
|
1830
|
+
1. **Generating the full script in one prompt** -- Faithfulness degrades in long output. Always generate per-segment with verification.
|
|
1831
|
+
2. **Skipping faithfulness verification** -- Without verification, 20-40% of claims in long scripts are fabricated. Always verify.
|
|
1832
|
+
3. **Fixed-size chunking** -- Splitting documents at 500-character boundaries splits sentences and loses context. Use semantic chunking at paragraph boundaries.
|
|
1833
|
+
4. **Single tone for all content** -- A peppy delivery for a paper about disease mortality is tone-deaf. Match tone to content.
|
|
1834
|
+
5. **Letting guests monologue** -- Max 4 sentences per turn. Real conversations have short turns with natural back-and-forth.
|
|
1835
|
+
6. **No source references in segments** -- Without `sourceReference` on each segment, you cannot trace claims back to verify them.
|
|
1836
|
+
7. **Using the same reactions repeatedly** -- "Great question!" five times in one episode sounds robotic. Vary reactions.
|
|
1837
|
+
|
|
1838
|
+
---
|
|
1839
|
+
|
|
1840
|
+
## Research Citations
|
|
1841
|
+
|