Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
dist
build
.next
out
coverage
pnpm-lock.yaml
*.tsbuildinfo
runs
11 changes: 11 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf"
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pnpm db:migrate # drizzle migrate

### Code style

- Default to **no comments**. Add one only when the *why* is non-obvious (a hidden constraint, a workaround, a surprising behavior). Never comment *what* — the code should already say that.
- Default to **no comments**. Add one only when the _why_ is non-obvious (a hidden constraint, a workaround, a surprising behavior). Never comment _what_ — the code should already say that.
- Small, focused modules. A file over ~200 lines is a signal to split.
- No premature abstractions. Three similar lines beat a clever generic. Wait for the third real use case before extracting.

Expand Down
18 changes: 9 additions & 9 deletions docs/CHANNELS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ research:
min_sources: <int>
recency_window_days: <int>
require_primary_sources: <bool>
source_balance: # required for any politically-charged channel
source_balance: # required for any politically-charged channel
- <source name or category>
- ...
blocked_sources: [] # optional deny-list
blocked_sources: [] # optional deny-list

bias_rules: # plain-language rules the fact-checker enforces
bias_rules: # plain-language rules the fact-checker enforces
- <rule>
- ...

Expand All @@ -46,13 +46,13 @@ jargon:

visual_style:
template: kinetic-explainer | <future templates>
palette: ["#hex", ...]
palette: ['#hex', ...]
font: <font family>
motion_intensity: subtle | balanced | snappy
broll_policy: stock_preferred | mixed | generated_allowed

publish:
auto_upload: <bool> # human approval still required until trust earned
auto_upload: <bool> # human approval still required until trust earned
shorts_variant: <bool>
description_template: <string>
```
Expand Down Expand Up @@ -98,7 +98,7 @@ jargon:

visual_style:
template: kinetic-explainer
palette: ["#0B2545", "#FFD700", "#F4F4F4"]
palette: ['#0B2545', '#FFD700', '#F4F4F4']
font: Inter
motion_intensity: balanced
broll_policy: mixed
Expand Down Expand Up @@ -135,7 +135,7 @@ research:
min_sources: 3
recency_window_days: 7
require_primary_sources: true
source_balance: # tech doesn't need political balance, but does need primary
source_balance: # tech doesn't need political balance, but does need primary
- vendor announcement / official docs
- independent reporting
- hands-on review or technical deep-dive
Expand All @@ -150,7 +150,7 @@ jargon:

visual_style:
template: kinetic-explainer
palette: ["#0F0F0F", "#00E5FF", "#FFFFFF"]
palette: ['#0F0F0F', '#00E5FF', '#FFFFFF']
font: JetBrains Mono
motion_intensity: snappy
broll_policy: generated_allowed
Expand All @@ -171,4 +171,4 @@ publish:

- **Don't** add `if (channel.id === '...') ...` in stage code. Add a schema field instead and check that.
- **Don't** rely on the channel id as anything other than an identifier. The behavior must come from explicit fields.
- **Don't** put prompts in the channel config. Prompts belong with their agents; channel configs feed *parameters* into prompts.
- **Don't** put prompts in the channel config. Prompts belong with their agents; channel configs feed _parameters_ into prompts.
94 changes: 47 additions & 47 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,62 @@ The elaborate phased plan. Each phase ends at a meaningful checkpoint where the

Establish the workspace, tooling, and conventions before any product code exists.

| # | Commit | Purpose |
|---|---|---|
| 1 | `chore: initialize pnpm workspace with strict TS, ESLint, Prettier` | Monorepo skeleton |
| 2 | `chore: add husky + lint-staged + commitlint` | Enforce conventions automatically |
| 3 | `chore: add .editorconfig, .gitignore, MIT license` | Hygiene |
| 4 | `chore(ci): GitHub Actions for lint + typecheck on PRs` | CI from day one |
| 5 | `chore: add .env.example documenting required keys` | Document secrets |
| # | Commit | Purpose |
| --- | ------------------------------------------------------------------- | --------------------------------- |
| 1 | `chore: initialize pnpm workspace with strict TS, ESLint, Prettier` | Monorepo skeleton |
| 2 | `chore: add husky + lint-staged + commitlint` | Enforce conventions automatically |
| 3 | `chore: add .editorconfig, .gitignore, MIT license` | Hygiene |
| 4 | `chore(ci): GitHub Actions for lint + typecheck on PRs` | CI from day one |
| 5 | `chore: add .env.example documenting required keys` | Document secrets |

**Exit criteria:** `pnpm install && pnpm typecheck && pnpm lint` all pass on an empty workspace.

### Phase 1 — Data and config

The shape of state and channel behavior.

| # | Commit | Purpose |
|---|---|---|
| 6 | `feat(db): drizzle schema for runs, stages, assets + Neon http driver` | Persistence layer |
| 7 | `feat(channels): zod schema for channel config + aussie-politics + tech` | Multi-channel parameterization |
| 8 | `feat(types): shared types package for pipeline ↔ web` | One source of truth for cross-package types |
| # | Commit | Purpose |
| --- | ------------------------------------------------------------------------ | ------------------------------------------- |
| 6 | `feat(db): drizzle schema for runs, stages, assets + Neon http driver` | Persistence layer |
| 7 | `feat(channels): zod schema for channel config + aussie-politics + tech` | Multi-channel parameterization |
| 8 | `feat(types): shared types package for pipeline ↔ web` | One source of truth for cross-package types |

**Exit criteria:** A new YAML in `packages/channels/configs/` is parseable and typed end-to-end. `pnpm db:generate` produces a clean migration.

### Phase 2 — Pipeline core, text stages

The brain of the system. No pixels yet.

| # | Commit | Purpose |
|---|---|---|
| 9 | `feat(pipeline): Inngest setup, Stage interface, run orchestrator` | Pipeline runtime |
| 10 | `feat(pipeline): stage 1 brief-builder agent` | One-liner → structured brief |
| 11 | `feat(pipeline): stage 2 researcher with citation enforcement` | Brief → fact pack with sources |
| 12 | `feat(pipeline): stage 4 scriptwriter with claim/source pairs` | Fact pack → script with structural citations |
| # | Commit | Purpose |
| --- | ------------------------------------------------------------------ | -------------------------------------------- |
| 9 | `feat(pipeline): Inngest setup, Stage interface, run orchestrator` | Pipeline runtime |
| 10 | `feat(pipeline): stage 1 brief-builder agent` | One-liner → structured brief |
| 11 | `feat(pipeline): stage 2 researcher with citation enforcement` | Brief → fact pack with sources |
| 12 | `feat(pipeline): stage 4 scriptwriter with claim/source pairs` | Fact pack → script with structural citations |

**Exit criteria:** Run the pipeline against `aussie-politics` and one test brief; get a complete, sourced script back as JSON. No UI yet — invoke through the Inngest dev server.

### Phase 3 — The cockpit

Human-in-the-loop UI. No new pipeline capability — just exposure of what already exists.

| # | Commit | Purpose |
|---|---|---|
| 13 | `feat(web): Next.js shell, runs list, new-run form` | App skeleton |
| 14 | `feat(web): run detail page with stage cards` | View / edit / approve per stage |
| 15 | `feat(web): wire stage approvals to Inngest waitForEvent` | The approval gate |
| 16 | `feat(web): stage output viewers (markdown, JSON, sources)` | Polish |
| # | Commit | Purpose |
| --- | ----------------------------------------------------------- | ------------------------------- |
| 13 | `feat(web): Next.js shell, runs list, new-run form` | App skeleton |
| 14 | `feat(web): run detail page with stage cards` | View / edit / approve per stage |
| 15 | `feat(web): wire stage approvals to Inngest waitForEvent` | The approval gate |
| 16 | `feat(web): stage output viewers (markdown, JSON, sources)` | Polish |

**Exit criteria:** From the browser, kick off a new run, watch stages progress, edit stage outputs if needed, approve each one. Final output: a finished script.

### Phase 4 — Audit and storyboard

The remaining text stages.

| # | Commit | Purpose |
|---|---|---|
| 17 | `feat(pipeline): stage 3 jargon miner` | Identify and define terms |
| 18 | `feat(pipeline): stage 5 fact-checker + stage 6 storyboarder` | Bias/citation audit + per-scene visual plan |
| # | Commit | Purpose |
| --- | ------------------------------------------------------------- | ------------------------------------------- |
| 17 | `feat(pipeline): stage 3 jargon miner` | Identify and define terms |
| 18 | `feat(pipeline): stage 5 fact-checker + stage 6 storyboarder` | Bias/citation audit + per-scene visual plan |

**Exit criteria:** Run produces a fully specced video as text: script + jargon definitions + per-scene visual plan + verified citations. Internally publishable.

Expand All @@ -84,24 +84,24 @@ Stop here. Generate 5–10 scripts across both channels. Read them critically. I

Pixels.

| # | Commit | Purpose |
|---|---|---|
| 19 | `feat(remotion): kinetic-explainer composition template` | Base visual template |
| 20 | `feat(remotion): channel-specific theming (politics, tech)` | Per-channel visual style |
| 21 | `feat(pipeline): stage 7 asset generator (TTS, stock, generated)` | All assets fetched/generated |
| 22 | `feat(pipeline): stage 8 assembler — Remotion render` | First video output |
| # | Commit | Purpose |
| --- | ----------------------------------------------------------------- | ---------------------------- |
| 19 | `feat(remotion): kinetic-explainer composition template` | Base visual template |
| 20 | `feat(remotion): channel-specific theming (politics, tech)` | Per-channel visual style |
| 21 | `feat(pipeline): stage 7 asset generator (TTS, stock, generated)` | All assets fetched/generated |
| 22 | `feat(pipeline): stage 8 assembler — Remotion render` | First video output |

**Exit criteria:** End-to-end run produces a watchable MP4. Quality is okay but not great.

### Phase 6 — QA and publish

The last mile.

| # | Commit | Purpose |
|---|---|---|
| 23 | `feat(pipeline): stage 9 QA reviewer (vision model watches output)` | Catches obvious issues |
| 24 | `feat(pipeline): stage 10 publisher (YouTube Data API)` | Upload with disclosure label |
| 25 | `feat(web): publish-approval UI with thumbnail/title/description editor` | Human approves final publish |
| # | Commit | Purpose |
| --- | ------------------------------------------------------------------------ | ---------------------------- |
| 23 | `feat(pipeline): stage 9 QA reviewer (vision model watches output)` | Catches obvious issues |
| 24 | `feat(pipeline): stage 10 publisher (YouTube Data API)` | Upload with disclosure label |
| 25 | `feat(web): publish-approval UI with thumbnail/title/description editor` | Human approves final publish |

**Exit criteria:** Click "publish" in the cockpit, video lands on YouTube with AI-content disclosure, metadata correct.

Expand All @@ -116,11 +116,11 @@ The last mile.

## Risks and mitigations

| Risk | Mitigation |
|---|---|
| Hallucinated facts in news/politics | Structural citations: every claim references a `source_id`. Fact-checker rejects unsourced claims. |
| Bias creep | Dedicated balance-check pass with config-driven source-balancing rules per channel. |
| Visual uncanny valley | Avoid AI avatars entirely. Lean on kinetic typography, stock, real news clips. Use generated b-roll only when storyboard explicitly calls for it. |
| Cost per video | Phase 2–4 are nearly free (just LLM calls). Phase 5+ adds TTS ($) and optional Veo b-roll ($$). Budget tracking added in Phase 5. |
| YouTube ToS | AI-disclosure label baked into publisher. Human approval required before upload until trust is established. |
| Channel-specific code leaking into core | Code review checklist: any `if (channel === ...)` triggers a refactor-to-config conversation. |
| Risk | Mitigation |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Hallucinated facts in news/politics | Structural citations: every claim references a `source_id`. Fact-checker rejects unsourced claims. |
| Bias creep | Dedicated balance-check pass with config-driven source-balancing rules per channel. |
| Visual uncanny valley | Avoid AI avatars entirely. Lean on kinetic typography, stock, real news clips. Use generated b-roll only when storyboard explicitly calls for it. |
| Cost per video | Phase 2–4 are nearly free (just LLM calls). Phase 5+ adds TTS ($) and optional Veo b-roll ($$). Budget tracking added in Phase 5. |
| YouTube ToS | AI-disclosure label baked into publisher. Human approval required before upload until trust is established. |
| Channel-specific code leaking into core | Code review checklist: any `if (channel === ...)` triggers a refactor-to-config conversation. |
49 changes: 49 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// @ts-check
import js from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/coverage/**',
'**/*.tsbuildinfo',
'runs/**',
],
},
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
Comment on lines +23 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js official docs, what version introduced import.meta.dirname, and is it available in Node v20.10.0?

💡 Result:

import.meta.dirname was introduced in Node.js v20.11.0 and v21.2.0 according to the official Node.js documentation[1]. The feature was added via a pull request merged on 2023-10-31[2][3], backported to the v20 branch[4], and released in v20.11.0 on 2024-01-10[5][6]. Node.js v20.10.0 was released earlier on 2023-11-22[5][7] and does not include this feature, as confirmed by the absence in changelogs and version-specific docs. Therefore, it is not available in Node v20.10.0.

Citations:


import.meta.dirname is incompatible with the declared Node engine floor.

import.meta.dirname was added in Node v20.11.0, but the repo allows >=20.10.0. On Node 20.10.x this config will fail to load.

💡 Proposed fix (pick one)
# Option A: keep current code, bump engine floor
-  "node": ">=20.10.0"
+  "node": ">=20.11.0"
# Option B: keep engine floor, avoid import.meta.dirname
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
...
-        tsconfigRootDir: import.meta.dirname,
+        tsconfigRootDir,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eslint.config.js` around lines 23 - 25, Replace the incompatible
import.meta.dirname usage for tsconfigRootDir: import and use
fileURLToPath(import.meta.url) and path.dirname instead (e.g., tsconfigRootDir:
path.dirname(fileURLToPath(import.meta.url))) so the ESLint config works on Node
>=20.10.0; update the top of eslint.config.js to import { fileURLToPath } from
'url' and path (or dirname from 'path') and reference the tsconfigRootDir
property and import.meta.url in the new expression.

},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
'@typescript-eslint/no-explicit-any': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
},
{
files: ['**/*.config.{js,mjs,ts}', '**/*.cjs'],
languageOptions: {
parserOptions: {
projectService: false,
},
},
...tseslint.configs.disableTypeChecked,
},
);
36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "videogenai",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "End-to-end pipeline: one-line description → researched, scripted, animated, voiced YouTube video, with human-approval cockpit.",
"license": "MIT",
"author": "Sai Charan",
"repository": {
"type": "git",
"url": "git+https://github.com/SaiCharan99/VideoGenAI.git"
},
"engines": {
"node": ">=20.10.0"
},
"packageManager": "pnpm@9.15.0",
"scripts": {
"build": "pnpm -r --if-present build",
"dev": "pnpm -r --parallel --if-present dev",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "pnpm -r --if-present typecheck",
"test": "pnpm -r --if-present test",
"clean": "pnpm -r --if-present clean && rm -rf node_modules"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@types/node": "^22.10.5",
"eslint": "^9.18.0",
"prettier": "^3.4.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
}
}
Loading