# 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.