Refactor AI daily report pipeline
This commit is contained in:
159
docs/plans/2026-06-04-local-dry-run-foundation.md
Normal file
159
docs/plans/2026-06-04-local-dry-run-foundation.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Local Dry-Run Foundation Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Make the current pipeline testable on a local machine without Hermes credentials, blog credentials, or live LLM calls.
|
||||
|
||||
**Architecture:** Keep the existing single script as the compatibility entrypoint. Add small, tested helpers for project `.env` loading, dry-run token behavior, and mock LLM responses. This creates a safe base for later Stage 0-8 modularization.
|
||||
|
||||
**Tech Stack:** Python standard library, `unittest`, current `script/ai_daily_blog_pipeline.py`.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Local `.env` Loading
|
||||
|
||||
**Files:**
|
||||
- Modify: `script/ai_daily_blog_pipeline.py`
|
||||
- Create: `tests/test_env_loading.py`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Test that `load_env()` reads project-root `.env` values when Hermes env is absent, and that real process environment variables override file values.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python -m unittest tests.test_env_loading -v`
|
||||
|
||||
Expected: FAIL because the script currently only reads `~/.hermes/.env`.
|
||||
|
||||
**Step 3: Implement minimal code**
|
||||
|
||||
Add a helper to parse env files and update `load_env()` to read:
|
||||
|
||||
1. Project `.env`
|
||||
2. `~/.hermes/.env`
|
||||
3. process environment
|
||||
|
||||
Later sources override earlier ones.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python -m unittest tests.test_env_loading -v`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 2: Let Dry-Run Skip Blog Token Requirement
|
||||
|
||||
**Files:**
|
||||
- Modify: `script/ai_daily_blog_pipeline.py`
|
||||
- Create: `tests/test_dry_run_config.py`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Extract a small helper such as `is_dry_run(env)` and `require_blog_token(env)`, then test:
|
||||
|
||||
- `AI_DAILY_DRY_RUN=1` does not require `BLOG_SERVICE_TOKEN`.
|
||||
- normal publish mode still requires a token.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python -m unittest tests.test_dry_run_config -v`
|
||||
|
||||
Expected: FAIL because no helper exists and `main()` checks token before dry-run.
|
||||
|
||||
**Step 3: Implement minimal code**
|
||||
|
||||
Move dry-run detection before token validation in `main()`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python -m unittest tests.test_dry_run_config -v`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: Add Mock LLM Mode
|
||||
|
||||
**Files:**
|
||||
- Modify: `script/ai_daily_blog_pipeline.py`
|
||||
- Create: `tests/test_mock_llm.py`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Test that `llm_call(prompt, {"AI_DAILY_LLM_MODE": "mock"})` returns valid JSON for:
|
||||
|
||||
- semantic dedup prompts
|
||||
- summary rewrite prompts
|
||||
- classify prompts
|
||||
|
||||
Also test that guide generation can get a non-empty mock response.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python -m unittest tests.test_mock_llm -v`
|
||||
|
||||
Expected: FAIL because mock mode does not exist.
|
||||
|
||||
**Step 3: Implement minimal code**
|
||||
|
||||
Add `AI_DAILY_LLM_MODE=mock` support in `llm_call()`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python -m unittest tests.test_mock_llm -v`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 4: Add Markdown Smoke Test
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/test_markdown_rendering.py`
|
||||
- Modify: `script/ai_daily_blog_pipeline.py` only if necessary.
|
||||
|
||||
**Step 1: Write the failing or characterization test**
|
||||
|
||||
Test that `blog_markdown()` renders:
|
||||
|
||||
- `## 导览`
|
||||
- at least one section
|
||||
- source links
|
||||
- no `> >`
|
||||
- no `[N]`
|
||||
|
||||
**Step 2: Run test**
|
||||
|
||||
Run: `python -m unittest tests.test_markdown_rendering -v`
|
||||
|
||||
Expected: If it already passes, keep it as characterization coverage. If it fails because of `> >`, implement a focused fix.
|
||||
|
||||
**Step 3: Implement minimal fix if needed**
|
||||
|
||||
Strip leading `>` from guide text before adding blockquote syntax.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python -m unittest tests.test_markdown_rendering -v`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 5: Run Full Verification
|
||||
|
||||
**Files:**
|
||||
- No new files.
|
||||
|
||||
**Step 1: Run unit tests**
|
||||
|
||||
Run: `python -m unittest discover -s tests -v`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
**Step 2: Run compile check**
|
||||
|
||||
Run: `python -m py_compile script/ai_daily_blog_pipeline.py`
|
||||
|
||||
Expected: exit code 0.
|
||||
|
||||
**Step 3: Check git status**
|
||||
|
||||
Run: `git status --short`
|
||||
|
||||
Expected: only intended files are modified or added.
|
||||
Reference in New Issue
Block a user