docs(04-analysis-orchestration): create phase plans

This commit is contained in:
2026-03-13 22:50:28 +08:00
parent debd53961a
commit 4b4ed9cab6
6 changed files with 1730 additions and 0 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,273 @@
---
phase: 04-analysis-orchestration
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- kb_pipeline/processors/base.py
- kb_pipeline/processors/parse.py
- kb_pipeline/processors/__init__.py
- kb_pipeline/schemas.py
- tests/test_processors/test_base.py
- tests/test_processors/test_parse.py
autonomous: true
requirements: [MNT-01]
user_setup: []
must_haves:
truths:
- "BaseProcessor defines unified interface for all processors"
- "ParseProcessor correctly parses Bilibili and Zhihu URLs"
- "All processors inherit from BaseProcessor with consistent process() method"
artifacts:
- path: "kb_pipeline/processors/base.py"
provides: "BaseProcessor abstract class"
contains: "class BaseProcessor"
min_lines: 80
- path: "kb_pipeline/processors/parse.py"
provides: "ParseProcessor implementation"
contains: "class ParseProcessor"
min_lines: 60
- path: "kb_pipeline/schemas.py"
provides: "ParseInput/ParseOutput schemas"
contains: "class ParseInput"
key_links:
- from: "kb_pipeline/processors/parse.py"
to: "kb_pipeline/link_parser.py"
via: "LinkParser.parse()"
pattern: "LinkParser\\(\\)\\.parse"
- from: "kb_pipeline/processors/base.py"
to: "kb_pipeline/state.py"
via: "WorkflowStateManager integration"
pattern: "state_manager\\."
---
<objective>
Create unified BaseProcessor interface and ParseProcessor to establish the foundation for the pipeline architecture.
Purpose: Provide consistent processor interface that all steps (parse, download, asr, analyze) will follow, enabling unified state management and error handling.
Output: BaseProcessor abstract class, ParseProcessor implementation, and related schemas.
</objective>
<execution_context>
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-analysis-orchestration/04-CONTEXT.md
@.planning/phases/04-analysis-orchestration/04-RESEARCH.md
<interfaces>
<!-- Key types and contracts from existing codebase. Executor should use these directly. -->
From kb_pipeline/schemas.py:
```python
class StepStatus(str, Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
class StepInput(BaseModel):
content_id: str = Field(..., min_length=1)
platform: Literal["bilibili", "zhihu"]
class StepOutput(BaseModel):
success: bool
error_message: Optional[str] = None
completed_at: datetime = Field(default_factory=datetime.now)
class WorkflowState(BaseModel):
def is_step_completed(self, step_name: str) -> bool: ...
def mark_step_started(self, step_name: str) -> None: ...
def mark_step_completed(self, step_name: str, output_summary: Optional[str] = None) -> None: ...
def mark_step_failed(self, step_name: str, error_message: str) -> None: ...
```
From kb_pipeline/state.py:
```python
class WorkflowStateManager:
STATE_FILENAME = "workflow_state.json"
def __init__(self, content_dir: Path): ...
def load(self) -> Optional[WorkflowState]: ...
def save(self, state: WorkflowState) -> None: ...
def get_or_create(self, content_id: str, platform: str, source_url: str) -> WorkflowState: ...
```
From kb_pipeline/link_parser.py:
```python
@dataclass
class ParsedLink:
platform: str
content_type: str
content_id: str
original_url: str
resolved_url: str = ""
class LinkParser:
def parse(self, url: str) -> Optional[ParsedLink]: ...
```
From kb_pipeline/processors/asr.py (existing pattern reference):
```python
class ASRProcessor:
STEP_NAME = "asr"
def __init__(self, state_manager: Optional[WorkflowStateManager] = None):
self.settings = get_settings()
self.state_manager = state_manager
async def process(self, input_data: ASRInput) -> ASROutput: ...
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Define BaseProcessor abstract class</name>
<files>kb_pipeline/processors/base.py, tests/test_processors/test_base.py</files>
<behavior>
- Test 1: BaseProcessor cannot be instantiated directly (abstract)
- Test 2: Subclass must implement process() method
- Test 3: BaseProcessor provides state_manager attribute
- Test 4: BaseProcessor provides STEP_NAME class attribute
</behavior>
<action>
Create `kb_pipeline/processors/base.py` with BaseProcessor abstract class:
1. Define abstract base class with:
- STEP_NAME: str class attribute (must be overridden by subclass)
- __init__(self, state_manager: Optional[WorkflowStateManager] = None)
- settings: KBSettings from get_settings()
- @abstractmethod process(self, input_data: StepInput) -> StepOutput
2. Use ABC (Abstract Base Class) from abc module
3. Import types from typing: Optional, TypeVar, Generic
4. Keep it minimal - no complex template methods, just the interface contract
Reference existing ASRProcessor pattern but extract the common interface.
Do NOT add retry logic - that belongs in concrete implementations.
Do NOT add complex inheritance hierarchies - keep it flat and simple.
</action>
<verify>
<automated>pytest tests/test_processors/test_base.py -v -x</automated>
</verify>
<done>BaseProcessor abstract class exists with process() method, cannot be instantiated directly</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Add ParseInput and ParseOutput schemas</name>
<files>kb_pipeline/schemas.py, tests/test_pipeline/test_schemas.py</files>
<behavior>
- Test 1: ParseInput validates url field (required, min_length=1)
- Test 2: ParseOutput contains platform, content_type, content_id, resolved_url
- Test 3: ParseOutput inherits success and error_message from StepOutput
</behavior>
<action>
Add ParseInput and ParseOutput schemas to `kb_pipeline/schemas.py`:
```python
class ParseInput(StepInput):
"""Parse step input - URL to parse"""
url: str = Field(..., min_length=1, description="URL to parse")
# Override content_id and platform as optional for parse step
content_id: str = Field(default="", description="Will be populated after parsing")
platform: str = Field(default="", description="Will be populated after parsing")
class ParseOutput(StepOutput):
"""Parse step output"""
platform: str = Field(..., description="Detected platform")
content_type: str = Field(..., description="Content type (video, article, etc.)")
content_id: str = Field(..., description="Extracted content ID")
resolved_url: str = Field(..., description="Resolved URL after short link expansion")
original_url: str = Field(..., description="Original URL provided by user")
```
Place after ASROutput schema (around line 236).
Add tests to existing test_schemas.py file.
</action>
<verify>
<automated>pytest tests/test_pipeline/test_schemas.py -v -x -k "Parse"</automated>
</verify>
<done>ParseInput and ParseOutput schemas exist and validate correctly</done>
</task>
<task type="auto" tdd="true">
<name>Task 3: Implement ParseProcessor</name>
<files>kb_pipeline/processors/parse.py, tests/test_processors/test_parse.py</files>
<behavior>
- Test 1: ParseProcessor parses Bilibili video URL correctly
- Test 2: ParseProcessor parses Zhihu answer URL correctly
- Test 3: ParseProcessor returns ParseOutput with success=False for unknown URLs
- Test 4: ParseProcessor records state when state_manager is provided
</behavior>
<action>
Create `kb_pipeline/processors/parse.py`:
1. Import BaseProcessor from .base, LinkParser from kb_pipeline.link_parser
2. Import ParseInput, ParseOutput from kb_pipeline.schemas
3. Implement ParseProcessor(BaseProcessor):
- STEP_NAME = "parse"
- async def process(self, input_data: ParseInput) -> ParseOutput:
a. Create LinkParser instance
b. Call parser.parse(input_data.url)
c. If parsed is None, return ParseOutput(success=False, error_message="Unsupported URL")
d. If parsed, return ParseOutput with parsed fields
e. Record state on success (if state_manager exists)
4. Keep implementation simple - just wrap LinkParser
5. Handle both success and failure cases with proper state recording
</action>
<verify>
<automated>pytest tests/test_processors/test_parse.py -v -x</automated>
</verify>
<done>ParseProcessor correctly parses URLs and returns structured ParseOutput</done>
</task>
<task type="auto">
<name>Task 4: Update processors __init__.py</name>
<files>kb_pipeline/processors/__init__.py</files>
<action>
Update `kb_pipeline/processors/__init__.py` to export new classes:
```python
from .base import BaseProcessor
from .parse import ParseProcessor
from .asr import ASRProcessor
__all__ = ["BaseProcessor", "ParseProcessor", "ASRProcessor"]
```
Keep existing ASRProcessor export and add new ones.
</action>
<verify>
<automated>python -c "from kb_pipeline.processors import BaseProcessor, ParseProcessor, ASRProcessor; print('OK')"</automated>
</verify>
<done>All processors can be imported from kb_pipeline.processors</done>
</task>
</tasks>
<verification>
- All tests pass: `pytest tests/test_processors/test_base.py tests/test_processors/test_parse.py -v`
- BaseProcessor is abstract and cannot be instantiated
- ParseProcessor correctly wraps LinkParser
- State recording works when state_manager is provided
</verification>
<success_criteria>
- BaseProcessor abstract class defines unified interface
- ParseProcessor inherits from BaseProcessor
- ParseInput/ParseOutput schemas exist and validate
- All tests pass
- Code follows existing patterns from ASRProcessor
</success_criteria>
<output>
After completion, create `.planning/phases/04-analysis-orchestration/04-01-SUMMARY.md`
</output>
@@ -0,0 +1,256 @@
---
phase: 04-analysis-orchestration
plan: 02
type: execute
wave: 2
depends_on: [04-01]
files_modified:
- kb_pipeline/processors/download.py
- kb_pipeline/schemas.py
- tests/test_processors/test_download.py
autonomous: true
requirements: [MNT-01]
user_setup: []
must_haves:
truths:
- "DownloadProcessor fetches metadata from platforms"
- "DownloadProcessor downloads content (video/audio or text)"
- "State is recorded after successful download"
artifacts:
- path: "kb_pipeline/processors/download.py"
provides: "DownloadProcessor implementation"
contains: "class DownloadProcessor"
min_lines: 100
- path: "kb_pipeline/schemas.py"
provides: "DownloadInput/DownloadOutput schemas"
contains: "class DownloadInput"
key_links:
- from: "kb_pipeline/processors/download.py"
to: "kb_pipeline/platforms/bilibili.py"
via: "BilibiliAdapter"
pattern: "BilibiliAdapter"
- from: "kb_pipeline/processors/download.py"
to: "kb_pipeline/platforms/zhihu.py"
via: "ZhihuAdapter"
pattern: "ZhihuAdapter"
- from: "kb_pipeline/processors/download.py"
to: "kb_pipeline/state.py"
via: "WorkflowStateManager"
pattern: "state_manager\\."
---
<objective>
Create DownloadProcessor to handle metadata extraction and content downloading for all platforms.
Purpose: Unify metadata and content fetching into a single processor that works with PlatformAdapter implementations.
Output: DownloadProcessor implementation with Bilibili and Zhihu adapter integration.
</objective>
<execution_context>
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-analysis-orchestration/04-CONTEXT.md
@.planning/phases/04-analysis-orchestration/04-RESEARCH.md
@.planning/phases/04-analysis-orchestration/04-01-SUMMARY.md
<interfaces>
<!-- Key types and contracts from existing code. Executor should use these directly. -->
From kb_pipeline/schemas.py:
```python
class StepInput(BaseModel):
content_id: str
platform: Literal["bilibili", "zhihu"]
class StepOutput(BaseModel):
success: bool
error_message: Optional[str]
completed_at: datetime
```
From kb_pipeline/processors/base.py (from 04-01):
```python
class BaseProcessor(ABC):
STEP_NAME: str
async def process(self, input_data: StepInput) -> StepOutput
async def _do_process(self, input_data: StepInput) -> StepOutput
```
From kb_pipeline/platforms/base.py:
```python
class PlatformAdapter(ABC):
@abstractmethod
async def fetch_metadata(self, content_id: str, content_type: str) -> Dict[str, Any]
@abstractmethod
async def fetch_content(self, content_id: str, content_type: str) -> str
@abstractmethod
def get_output_filename(self, content_id: str, content_type: str, suffix: str) -> str
```
From kb_pipeline/platforms/bilibili.py:
```python
class BilibiliAdapter(PlatformAdapter):
# Already implemented with fetch_metadata and fetch_content
```
From kb_pipeline/platforms/zhihu.py:
```python
class ZhihuAdapter(PlatformAdapter):
# Already implemented with fetch_metadata and fetch_content
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Define Download Schemas</name>
<files>kb_pipeline/schemas.py, tests/test_processors/test_download.py</files>
<behavior>
- Test 1: DownloadInput validates required fields
- Test 2: DownloadOutput validates required fields
- Test 3: DownloadOutput handles both video and article content types
</behavior>
<action>
Add DownloadInput and DownloadOutput schemas to kb_pipeline/schemas.py.
**DownloadInput fields (extends StepInput):**
- content_dir: str - Directory to save downloaded content
- content_type: str - Type of content (video, article, answer, zhuanlan)
**DownloadOutput fields (extends StepOutput):**
- metadata_file: str - Path to metadata JSON
- content_file: str - Path to content file (video/audio or text)
- title: str - Content title
- author: str - Content author
- duration: Optional[int] - Duration for video content
Follow StepInput/StepOutput pattern from Phase 1.
Create test file with schema validation tests.
</action>
<verify>
<automated>pytest tests/test_processors/test_download.py -v -x</automated>
</verify>
<done>DownloadInput/DownloadOutput schemas defined and validated with tests</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Create DownloadProcessor with metadata fetching</name>
<files>kb_pipeline/processors/download.py, tests/test_processors/test_download.py</files>
<behavior>
- Test 1: DownloadProcessor fetches Bilibili video metadata correctly
- Test 2: DownloadProcessor fetches Zhihu answer metadata correctly
- Test 3: DownloadProcessor records state on success
- Test 4: DownloadProcessor records state on failure
</behavior>
<action>
Create `kb_pipeline/processors/download.py` with DownloadProcessor class.
**Implementation:**
1. Import BaseProcessor, PlatformAdapter classes
2. Import BilibiliAdapter, ZhihuAdapter from platforms
3. Create DownloadProcessor(BaseProcessor):
- STEP_NAME = "download"
- __init__ with state_manager, settings
- process() method that:
a. Checks if step already completed (skip if done)
b. Gets appropriate PlatformAdapter based on platform
c. Calls adapter.fetch_metadata()
d. Saves metadata to content_dir
e. Records state on success/failure
**State Recording:**
- On success: mark_step_completed("download", f"title={title}, type={content_type}")
- On failure: mark_step_failed("download", error_message)
Create comprehensive tests mocking adapters and state_manager.
</action>
<verify>
<automated>pytest tests/test_processors/test_download.py -v -x</automated>
</verify>
<done>DownloadProcessor fetches metadata with state recording</done>
</task>
<task type="auto" tdd="true">
<name>Task 3: Add content downloading to DownloadProcessor</name>
<files>kb_pipeline/processors/download.py, tests/test_processors/test_download.py</files>
<behavior>
- Test 1: DownloadProcessor downloads Bilibili article content
- Test 2: DownloadProcessor skips video download (handled by ASR)
- Test 3: DownloadProcessor creates correct output file paths
</behavior>
<action>
Extend DownloadProcessor to download content.
**For articles/text content:**
- Call adapter.fetch_content() to get text
- Save content to content_dir as Markdown file
- Update DownloadOutput with content_file path
**For video content:**
- Skip actual video download (handled by ASR in Phase 3)
- Set content_file to empty or indicate video needs ASR
**File naming:**
- Metadata: `{platform}_{content_id}_metadata.json`
- Content: `{platform}_{content_id}_content.md` (for articles)
Update tests to verify content downloading behavior.
</action>
<verify>
<automated>pytest tests/test_processors/test_download.py -v -x</automated>
</verify>
<done>DownloadProcessor downloads content for text-based content types</done>
</task>
<task type="auto">
<name>Task 4: Update processors __init__.py</name>
<files>kb_pipeline/processors/__init__.py</files>
<action>
Update `kb_pipeline/processors/__init__.py` to export DownloadProcessor.
```python
from .base import BaseProcessor
from .parse import ParseProcessor
from .download import DownloadProcessor
from .asr import ASRProcessor
__all__ = ["BaseProcessor", "ParseProcessor", "DownloadProcessor", "ASRProcessor"]
```
</action>
<verify>
<automated>python -c "from kb_pipeline.processors import DownloadProcessor; print('OK')"</automated>
</verify>
<done>DownloadProcessor exported from processors module</done>
</task>
</tasks>
<verification>
- All tests pass: `pytest tests/test_processors/test_download.py -v`
- DownloadProcessor correctly uses PlatformAdapter implementations
- State recording works for both success and failure cases
- Content files are saved to correct locations
</verification>
<success_criteria>
- DownloadProcessor inherits from BaseProcessor
- DownloadInput/DownloadOutput schemas exist and validate
- Metadata is fetched and saved correctly
- Text content is downloaded and saved
- Video content is marked for ASR processing
- All tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/04-analysis-orchestration/04-02-SUMMARY.md`
</output>
@@ -0,0 +1,255 @@
---
phase: 04-analysis-orchestration
plan: 03
type: execute
wave: 3
depends_on: [04-01, 04-02]
files_modified:
- kb_pipeline/processors/analyze.py
- kb_pipeline/schemas.py
- tests/test_processors/test_analyze.py
autonomous: true
requirements: [MNT-01]
user_setup: []
must_haves:
truths:
- "AnalyzeProcessor generates AI analysis reports"
- "Analysis reports are saved as Markdown files"
- "AnalyzeProcessor wraps existing DeepAnalyzer"
artifacts:
- path: "kb_pipeline/processors/analyze.py"
provides: "AnalyzeProcessor implementation"
contains: "class AnalyzeProcessor"
min_lines: 100
- path: "kb_pipeline/schemas.py"
provides: "AnalyzeInput/AnalyzeOutput schemas"
contains: "class AnalyzeInput"
- path: "tests/test_processors/test_analyze.py"
provides: "Comprehensive test suite"
contains: "test_analyze_processor"
key_links:
- from: "kb_pipeline/processors/analyze.py"
to: "kb-deep-analyzer/deep_analyzer.py"
via: "DeepAnalyzer class import"
pattern: "from.*deep_analyzer.*import"
- from: "kb_pipeline/processors/analyze.py"
to: "kb_pipeline/state.py"
via: "WorkflowStateManager"
pattern: "state_manager\\."
---
<objective>
Create AnalyzeProcessor to wrap existing DeepAnalyzer and integrate it into the unified processor pipeline.
Purpose: Provide AI-powered content analysis that generates structured reports as Markdown files.
Output: AnalyzeProcessor implementation with DeepAnalyzer integration.
</objective>
<execution_context>
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-analysis-orchestration/04-CONTEXT.md
@.planning/phases/04-analysis-orchestration/04-RESEARCH.md
@.planning/phases/04-analysis-orchestration/04-01-SUMMARY.md
@.planning/phases/04-analysis-orchestration/04-02-SUMMARY.md
<interfaces>
<!-- Key types from existing code. Executor should use these directly. -->
From kb-deep-analyzer/deep_analyzer.py:
```python
class SimpleAIClient:
def __init__(self, api_key: str = None, provider: str = None, model: str = None, base_url: str = None):
...
def generate(self, prompt: str, max_tokens: int = 16000) -> str:
...
class DeepAnalyzer:
def __init__(self, ai_client: SimpleAIClient = None):
...
def analyze(self, metadata: Dict[str, Any], content: Dict[str, Any], content_type: str = "video") -> AnalysisResult:
...
@dataclass
class AnalysisResult:
summary: str = ""
main_topics: List[str] = field(default_factory=list)
key_insights: List[str] = field(default_factory=list)
recommended_tags: List[str] = field(default_factory=list)
full_analysis: str = ""
original_text: str = ""
def generate_frontmatter(metadata: Dict, result: AnalysisResult, content_type: str = "video") -> str:
...
```
From kb_pipeline/schemas.py (after 04-01 and 04-02):
```python
class StepInput(BaseModel):
content_id: str
platform: Literal["bilibili", "zhihu"]
class StepOutput(BaseModel):
success: bool
error_message: Optional[str]
completed_at: datetime
```
From kb_pipeline/processors/base.py (from 04-01):
```python
class BaseProcessor(ABC):
STEP_NAME: str
async def process(self, input_data: StepInput) -> StepOutput
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Define Analyze Schemas</name>
<files>kb_pipeline/schemas.py, tests/test_processors/test_analyze.py</files>
<behavior>
- Test 1: AnalyzeInput validates required fields
- Test 2: AnalyzeOutput validates required fields
- Test 3: AnalyzeOutput handles both video and article content types
</behavior>
<action>
Add AnalyzeInput and AnalyzeOutput schemas to kb_pipeline/schemas.py.
**AnalyzeInput fields (extends StepInput):**
- metadata_file: str - Path to metadata JSON file
- content_file: str - Path to content file (subtitle JSON or article content)
- content_type: str - Type of content (video or article)
- output_file: str - Path to output analysis Markdown file
**AnalyzeOutput fields (extends StepOutput):**
- analysis_file: str - Path to generated analysis report
- summary: str - Content summary
- main_topics: List[str] - Main topics extracted
- key_insights: List[str] - Key insights
- tags: List[str] - Recommended tags
- stats: Dict[str, Any] - Statistics about analysis
Create test file with schema validation tests.
</action>
<verify>
<automated>pytest tests/test_processors/test_analyze.py -v -x -k "test_schema"</automated>
</verify>
<done>AnalyzeInput/AnalyzeOutput schemas defined and validated</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Create AnalyzeProcessor with DeepAnalyzer integration</name>
<files>kb_pipeline/processors/analyze.py, tests/test_processors/test_analyze.py</files>
<behavior>
- Test 1: AnalyzeProcessor loads metadata and content from files
- Test 2: AnalyzeProcessor calls DeepAnalyzer.analyze() with correct parameters
- Test 3: AnalyzeProcessor saves analysis report as Markdown
- Test 4: AnalyzeProcessor records state on success
- Test 5: AnalyzeProcessor records state on failure
</behavior>
<action>
Create `kb_pipeline/processors/analyze.py` with AnalyzeProcessor class.
**Implementation:**
1. Import BaseProcessor from .base
2. Import DeepAnalyzer, SimpleAIClient, AnalysisResult, generate_frontmatter from kb_deep_analyzer
3. Import AnalyzeInput, AnalyzeOutput from ..schemas
4. Create AnalyzeProcessor(BaseProcessor):
- STEP_NAME = "analyze"
- __init__ with state_manager, settings (initialize AI client lazily)
- process() method that:
a. Checks if step already completed (skip if done)
b. Loads metadata JSON file
c. Loads content JSON/text file
d. Initializes SimpleAIClient with settings
e. Creates DeepAnalyzer instance
f. Calls analyzer.analyze(metadata, content, content_type)
g. Generates frontmatter using generate_frontmatter()
h. Writes analysis report as Markdown
i. Records state on success/failure
**Output file format:**
```markdown
---
title: "..."
date: ...
author: ...
tags: [...]
---
## 标题
...
## 摘要
...
## 主要主题
...
## 关键见解
...
## 时间轴 (for video only)
...
```
Create comprehensive tests with mocked DeepAnalyzer.
</action>
<verify>
<automated>pytest tests/test_processors/test_analyze.py -v -x</automated>
</verify>
<done>AnalyzeProcessor generates analysis reports using DeepAnalyzer</done>
</task>
<task type="auto">
<name>Task 3: Update processors __init__.py</name>
<files>kb_pipeline/processors/__init__.py</files>
<action>
Update `kb_pipeline/processors/__init__.py` to export AnalyzeProcessor.
```python
from .base import BaseProcessor
from .parse import ParseProcessor
from .download import DownloadProcessor
from .asr import ASRProcessor
from .analyze import AnalyzeProcessor
__all__ = ["BaseProcessor", "ParseProcessor", "DownloadProcessor", "ASRProcessor", "AnalyzeProcessor"]
```
</action>
<verify>
<automated>python -c "from kb_pipeline.processors import AnalyzeProcessor; print('OK')"</automated>
</verify>
<done>AnalyzeProcessor exported from processors module</done>
</task>
</tasks>
<verification>
- All tests pass: `pytest tests/test_processors/test_analyze.py -v`
- AnalyzeProcessor correctly wraps DeepAnalyzer
- Analysis reports are saved as Markdown files
- Frontmatter is generated correctly
- State recording works for both success and failure cases
</verification>
<success_criteria>
- AnalyzeProcessor inherits from BaseProcessor
- AnalyzeInput/AnalyzeOutput schemas exist and validate
- DeepAnalyzer is integrated correctly (no modifications to existing code)
- Analysis reports are saved with proper frontmatter
- All tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/04-analysis-orchestration/04-03-SUMMARY.md`
</output>
@@ -0,0 +1,658 @@
---
phase: 04-analysis-orchestration
plan: 04
type: execute
wave: 4
depends_on: [04-01, 04-02, 04-03]
files_modified:
- kb_pipeline/pipeline.py
- kb_pipeline/processors/__init__.py
- kb-workflow/kb_workflow.py
- tests/test_pipeline/test_pipeline.py
- tests/test_cli/test_process_command.py
autonomous: true
requirements: [MNT-01]
user_setup: []
must_haves:
truths:
- "Pipeline orchestrestr executes full workflow from URL to analysis"
- "Single command triggers entire workflow"
- "Progress is visible through simple logging"
- "New developers can understand code in 30 minutes"
artifacts:
- path: "kb_pipeline/pipeline.py"
provides: "Pipeline engine class"
min_lines: 100
- path: "kb-workflow/kb_workflow.py"
provides: "CLI entry point with process subcommand"
min_lines: 50
- path: "tests/test_pipeline/test_pipeline.py"
provides: "Pipeline tests"
min_lines: 100
- path: "tests/test_cli/test_process_command.py"
provides: "CLI tests"
key_links:
- from: "kb_pipeline/pipeline.py"
to: "kb_pipeline/processors"
via: "Pipeline.run()"
pattern: "Pipeline\\("
- from: "kb-workflow/kb_workflow.py"
to: "kb_pipeline.processors"
via: "import"
pattern: "from.* import"
- from: "kb_pipeline/processors" import ParseProcessor, DownloadProcessor, ASrProcessor, AnalyzeProcessor
pattern: "from.* import.*"
---
<objective>
Create Pipeline engine to orchest all processors and execute the complete workflow from URL to analysis report.
Purpose: Enable single-command workflow execution with automatic state management and error recovery, and and new developer to easily understand and code结构.
Output: Pipeline engine class, process subcommand, and CLI entry point.
</objective>
<execution_context>
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-analysis-orchestration/04-CONTEXT.md
@.planning/phases/04-analysis-orchestration/04-RESEARCH.md
@.planning/phases/04-analysis-orchestration/04-01-SUMMARY.md
@.planning/phases/04-analysis-orchestration/04-02-SUMMARY.md
@.planning/phases/04-analysis-orchestration/04-03-SUMMARY.md
<interfaces>
<!-- Key types and contracts from existing code. Executor should use these directly. -->
From kb_pipeline/processors/base.py:
```python
class BaseProcessor(ABC):
STEP_NAME: str
async def process(self, input_data: StepInput) -> StepOutput:
```
From kb_pipeline/processors/parse.py:
```python
class ParseProcessor(BaseProcessor):
STEP_NAME = "parse"
async def process(self, input_data: ParseInput) -> ParseOutput
```
from kb_pipeline/processors/download.py:
```python
class DownloadProcessor(BaseProcessor):
STEP_NAME = "download"
async def process(self, input_data: DownloadInput) -> DownloadOutput
```
from kb_pipeline/processors/asr.py:
```python
class ASRProcessor:
STEP_NAME = "asr"
async def process(self, input_data: ASRInput) -> ASROutput
```
from kb_pipeline/processors/analyze.py:
```python
class AnalyzeProcessor(BaseProcessor):
STEP_NAME = "analyze"
async def process(self, input_data: AnalyInput) -> AnalyOutput
```
from kb_pipeline/schemas.py:
```python
class StepInput(BaseModel):
content_id: str = platform: Literal["bilibili", "zhihu"]
class StepOutput(BaseModel):
success: bool
error_message: Optional[str]
completed_at: datetime
class ParseInput(StepInput):
url: str
class ParseOutput(StepOutput):
parsed: ParsedLink
content_dir: Path
metadata_file: Optional[str] = None
content_file: Optional[str] = None
content_type: str = "video" # video needs AS
# article不需要
content_file: Optional[str] = None # 从 download步骤获取
content_type: str # "video" or "article"
download_input.content_dir
download_output(
success=True,
metadata_file=f"{content_dir}/{platform}_{content_id}_metadata.json",
content_file=content_file,
title=metadata.get('title', '未知'),
author=metadata.get('author', '未知'),
duration=metadata.get('duration') # seconds, )
return state.mark_step_completed(
self.STEP_NAME, f"title={title}, author={author}, type={content_type}"
)
self.state_manager.save(state)
return output
```
from kb_pipeline.state import WorkflowStateManager
from kb_pipeline.config import get_settings
from kb_pipeline.errors import PipelineError
from kb_pipeline.processors.parse import ParseProcessor
from kb_pipeline.processors.download import DownloadProcessor
from kb_pipeline.processors.asr import ASRProcessor
from kb_pipeline.processors.analyze import AnalyzeProcessor
from typing import List, Optional, Dict, Any, TYPE_CHECKING
from pathlib import Path
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
class PipelineConfig:
"""Pipeline configuration"""
steps: List[str] = Field(default_factory=lambda: ["parse", "download", "asr", "analyze"])
output_dir: Path
skip_completed: bool = Field(default=True, description="Skip completed steps")
max_retries: int = Field(default=3, description="Max retries for each step")
class Pipeline:
"""Pipeline engine - orchestrates multiple processors
Orchestrates the workflow:
1. Parse URL to extract platform and content ID
2. Run processors in order (parse, download, asr, analyze)
3. Handle errors and retry on failures
4. Return results
Key decisions:
- Sequential execution (not parallel - simpler to easier to debug
- State persistence after each step (via WorkflowStateManager)
- Progress logging to simple, clear
"""
def __init__(
self,
processors: List[BaseProcessor],
config: Optional[PipelineConfig] = None,
output_dir: Optional[Path] = None,
skip_completed: bool = False
):
self.processors = processors
self.config = config or PipelineConfig.default()
self.output_dir = output_dir
self.skip_completed = skip_completed
# Validate processors
for processor in self.processors:
if processor.STEP_NAME not in self.config.steps:
raise ValueError(f"Unknown processor: {processor.STEP_NAME}")
async def run(self, url: str) -> Dict[str, Any]:
"""Execute full workflow
Args:
url: URL to process
Returns:
Dict containing all step results
"""
results: Dict[str, Any] = {}
current_step = 0
total_steps = len(self.config.steps)
state: Optional[WorkflowState] = None
try:
for step_name in self.config.steps:
current_step += 1
logger.info(f"[Step {current_step}/{total_steps}] {step_name}...")
# Get processor
processor = self.processors.get(step_name)
if not processor:
raise ValueError(f"Processor not found: {step_name}")
# Get content directory from parsed URL
parsed = await self._parse_url(url)
if not parsed:
results["error"] = "Failed to parse URL"
return results
# Get or create content directory
content_dir = self.output_dir / f"{parsed.platform}_{parsed.content_id}"
content_dir.mkdir(parents=True, exist_ok=True)
# Initialize state manager for this content
state_manager = WorkflowStateManager(content_dir)
# Run each step
previous_result = None
for step_name in self.config.steps:
# Check if already completed
if state and state.is_step_completed(step_name):
current_step += 1
logger.info(f"[Step {current_step}/{total_steps}] {step_name} - skipping (already completed)")
results[step_name] = {"status": "skipped", "message": "Already completed"}
continue
# Create step input
input_data = self._create_step_input(
step_name=step_name,
parsed=parsed,
state=state,
previous_result=previous_result
)
# Run processor
try:
output = await processor.process(input_data)
results[step_name] = {
"status": "success" if output.success else "failed",
"output": output.model_dump() if output.success else None,
"error": output.error_message
}
previous_result = output
except Exception as e:
results[step_name] = {
"status": "failed",
"error": str(e)
}
logger.error(f"[Step {current_step}/{total_steps}] {step_name} - failed: {e}")
# Don't stop pipeline on failure
raise
return results
def _parse_url(self, url: str) -> Optional[ParsedLink]:
"""Parse URL using LinkParser"""
if not url:
return None
parser = LinkParser()
parsed = parser.parse(url)
if not parsed:
logger.warning(f"Failed to parse URL: {url}")
return None
return ParsedLink
def _create_step_input(
self,
step_name: str,
parsed: ParsedLink,
state: Optional[WorkflowState],
previous_result: Optional[Dict[str, Any]],
) -> Dict[str, Any]:
"""Create step input based on step name and previous result"""
step_map = {
"parse": ParseProcessor,
"download": DownloadProcessor,
"asr": ASRProcessor,
"analyze": AnalyzeProcessor,
}
# Get processor instance
processor = self.processors[step_name]
if not processor:
raise ValueError(f"Unknown processor: {step_name}")
return step_map
async def _run_step(
self,
step_name: str,
input_data: Dict[str, Any],
state: Optional[WorkflowState],
previous_result: Optional[Dict[str, Any]]
) -> StepOutput:
"""Run a single step"""
processor = self.processors[step_name]
if not processor:
raise ValueError(f"No processor for {step_name}")
# Check if already completed
if state and state.is_step_completed(step_name):
logger.info(f"Step {step_name} already completed, skipping")
return self._create_skipped_output()
try:
# Run the
if previous_result and previous_result.get(step_name):
input_data = previous_result[step_name]
else:
raise ValueError(f"No previous result for {step_name}")
return await processor.process(input_data)
except Exception as e:
logger.error(f"Step {step_name} failed: {e}")
# Record failure
if state:
state.mark_step_failed(step_name, str(e))
self.state_manager.save(state)
raise
# Update previous result
previous_result[step_name] = output.model_dump()
return results
def get_output_dir(self) -> Path:
return Path(self.output_dir) / Path(self.config.output_dir)
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Define Pipeline Schemas</name>
<files>kb_pipeline/schemas.py, tests/test_pipeline/test_pipeline.py</files>
<behavior>
- Test 1: PipelineInput validates required fields
- Test 2: PipelineOutput validates required fields
- Test 3: PipelineConfig default steps are correct
</behavior>
<action>
Add PipelineInput, PipelineOutput, and PipelineConfig to kb_pipeline/schemas.py.
**PipelineInput fields (extends nothing - all fields inherited from StepInput):**
- content_dir: str - Directory to save downloaded content
- content_type: str - Type of content (video, article, answer, zhuanlan)
- skip_completed: bool - Whether to skip already completed steps (default True, content_type not to ["video", "article", "answer", "zhuanlan", "download", "asr", "analyze"]
- steps: List[str] = Field(...)
output_dir: Path
skip_completed: bool = True
</action>
<verify>
<automated>pytest tests/test_pipeline/test_pipeline.py -v -x -k"test_schema" in</automated>
</verify>
<done>PipelineInput/Output schemas defined and validated</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Create Pipeline class with processor initialization</name>
<files>kb_pipeline/pipeline.py, tests/test_pipeline/test_pipeline.py</files>
<behavior>
- Test 1: Pipeline initializes with all processors
- Test 2: Pipeline.parse_url creates correct content directory
- Test 3: Pipeline.run() executes all steps in correct order
- Test 4: Pipeline.run() handles step skipping correctly
- Test 5: Pipeline.run() handles errors and continues to next step
- Test 6: Pipeline.run() records state for each step
</behavior>
<action>
Create `kb_pipeline/pipeline.py` with Pipeline class.
**Implementation:**
1. Initialize with processors list (ParseProcessor, DownloadProcessor, ASRProcessor, AnalyzeProcessor)
2. Set config with PipelineConfig.default()
3. Set output_dir
4. Initialize state_manager (WorkflowStateManager)
5. Implement run() method that:
- Creates content directory
- Runs steps in order (parse, download, asr, analyze)
- Returns results dict
6. Add helper methods:
- _parse_url(url) -> ParsedLink
- _get_content_dir(parsed)
- _run_step(step_name, parsed, state, previous_result)
- _record_state
7. Add error handling with try/except
- logger.error for error message
if self.state_manager:
state.mark_step_failed(step_name, str(e))
self.state_manager.save(state)
raise
def _parse_url(self, url: str) -> Optional[ParsedLink]:
"""Parse URL using LinkParser"""
if not url:
return None
parser = LinkParser()
parsed = parser.parse(url)
if not parsed:
return None
return Parsed
def _get_content_dir(self, parsed: ParsedLink) -> Path:
"""Get content directory from parsed URL"""
content_dir = self.output_dir / f"{parsed.platform}_{parsed.content_id}"
content_dir.mkdir(parents=True, exist_ok=True)
return content_dir
def _create_step_input(
self,
step_name: str,
parsed: ParsedLink,
state: Optional[WorkflowState],
previous_result: Optional[Dict[str, Any]]
) -> Dict[str, Any]:
"""Create step input based on step name and previous result"""
step_map = {
"parse": ParseProcessor,
"download": DownloadProcessor,
"asr": ASRProcessor,
"analyze": AnalyzeProcessor
}
# Get processor instance
processor = self.processors[step_name]
if not processor:
raise ValueError(f"Unknown processor: {step_name}")
return step_map
async def _run_step(
self,
step_name: str,
input_data: Dict[str, Any],
state: Optional[WorkflowState],
previous_result: Optional[Dict[str, Any]],
) -> StepOutput:
"""Run a single step"""
processor = self.processors[step_name]
if not processor:
raise ValueError(f"No processor for {step_name}")
# Check if already completed
if state and state.is_step_completed(step_name):
logger.info(f"Step {step_name} already completed, skipping")
return self._create_skipped_output()
try:
# Run step
if previous_result and previous_result.get(step_name):
input_data = previous_result[step_name]
else:
raise ValueError(f"No previous result for {step_name}")
return await processor.process(input_data)
except Exception as e:
logger.error(f"Step {step_name} failed: {e}")
if state:
state.mark_step_failed(step_name, str(e))
self.state_manager.save(state)
raise
# Update previous result
previous_result[step_name] = output.model_dump()
return results
def _create_skipped_output(self) -> StepOutput:
return StepOutput(success=True, error_message="Skipped: already completed")
</task>
<task type="auto">
<name>Task 3: Add step execution logic</name>
<files>kb_pipeline/pipeline.py</files>
<action>
Extend Pipeline.run() to implement step execution logic:
1. Create content directory
2. Run steps in order
3. Handle step skipping (check state)
4. Handle errors (try/except, log and continue)
5. Record final state
6. Return results
**Step execution logic:**
```python
async def _execute_steps(
self,
parsed: ParsedLink,
state: WorkflowState,
previous_result: Dict[str, Any]
) -> None:
for step_name in self.config.steps:
step_start = datetime.now()
state.mark_step_started(step_name)
logger.info(f"[Step {step_num}/{total_steps}] {step_name}...")
# Get or run processor
processor = self.processors[step_name]
if not processor:
raise ValueError(f"Unknown processor: {step_name}")
# Get step input
input_data = self._create_step_input(
step_name=step_name,
parsed=parsed,
state=state,
previous_result=previous_result.get(step_name)
)
# Run processor
try:
output = await processor.process(input_data)
previous_result[step_name] = output.model_dump()
results[step_name] = {
"status": "success" if output.success else "failed",
"output": output.model_dump() if output.success else None,
"error": output.error_message
}
previous_result = output
except Exception as e:
results[step_name] = {
"status": "failed",
"error": str(e)
}
logger.error(f"[Step {step_num}/{total_steps}] {step_name} - failed: {e}")
# Record failure
if state:
state.mark_step_failed(step_name, str(e))
self.state_manager.save(state)
# Stop on failure
raise
return results
</action>
<verify>
<automated>pytest tests/test_pipeline/test_pipeline.py -v -x</automated>
</verify>
<done>Pipeline engine executes workflow with state persistence</done>
</task>
<task type="auto">
<name>Task 4: Add CLI process command</name>
<files>kb-workflow/kb_workflow.py</files>
<action>
Extend kb-workflow/kb_workflow.py to add process subcommand.
**CLI additions:**
- New argument: 'url' with help='URL to process'
- New argument: '--output-dir', '-o' with help='Output directory (default: KB_BASE_PATH/content)
- New argument: '--no-analyze', action='store_true', help='Skip analysis step')
- Set default to process function that handles the command
- Call Pipeline().run(url, output_dir, skip_completed=no)
- Print success message
- If error: print error message and exit(1)
**Update existing help text and kb_workflow.py to document the new command.
</action>
<verify>
<automated>python kb-workflow/kb_workflow.py --help | grep process</automated>
</verify>
<done>CLI process command added and functional</done>
</task>
<task type="auto">
<name>Task 5: Create Pipeline tests</name>
<files>tests/test_pipeline/test_pipeline.py,files>
<action>
Create comprehensive tests for Pipeline class.
**Test cases:**
1. Test pipeline initialization
2. Test URL parsing and content dir creation
3. Test step execution order
4. Test step skipping
5. Test error handling and6. Test state persistence
7. Test full workflow (mocked)
Use fixtures for mocking processors, state_manager, settings.
</action>
<verify>
<automated>pytest tests/test_pipeline/test_pipeline.py -v</automated>
</verify>
<done>Pipeline class fully tested</done>
</task>
</tasks>
<verification>
- All tests pass: `pytest tests/test_pipeline/test_pipeline.py tests/test_cli/test_process_command.py -v`
- Pipeline.run() executes workflow correctly
- State persistence works
- Error handling works
- Step skipping works
</verification>
<success_criteria>
- Pipeline class orchestrates all processors
- Pipeline.run() executes workflow from URL to analysis
- Content directory created correctly
- Steps executed in order
- State persisted after each step
- Errors handled with retry
- Completed steps skipped
- CLI process command works
</success_criteria>
<output>
After completion, create `.planning/phases/04-analysis-orchestration/04-04-SUMMARY.md`
</output>
@@ -0,0 +1,288 @@
---
phase: 04-analysis-orchestration
plan: 05
type: execute
wave: 5
depends_on: [04-04]
files_modified:
- README.md
- tests/test_integration/test_e2e_workflow.py
autonomous: false
requirements: [MNT-01]
user_setup: []
must_haves:
truths:
- "End-to-end workflow completes successfully"
- "Analysis report is saved to content directory"
- "workflow_state.json correctly records all steps"
artifacts:
- path: "README.md"
provides: "Project documentation"
contains: "## Installation"
- path: "tests/test_integration/test_e2e_workflow.py"
provides: "End-to-end integration test"
contains: "test_e2e"
key_links:
- from: "kb_pipeline/pipeline.py"
to: "kb_pipeline/processors"
via: "import processors"
pattern: "from.*processor import"
- from: "kb-workflow/kb_workflow.py"
to: "kb_pipeline"
via: "import pipeline"
pattern: "from.*pipeline import"
---
<objective>
Create end-to-end integration test and documentation ( project overview, installation, and and usage.
Purpose: Verify the complete workflow works and provide clear project documentation for maintainability.
Output: README.md with project overview, tests/test_integration/test_e2e_workflow.py
</objective>
<execution_context>
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-analysis-orchestration/04-CONTEXT.md
@.planning/phases/04-analysis-orchestration/04-RESEARCH.md
@.planning/phases/04-analysis-orchestration/04-04-SUMMARY.md
<interfaces>
<!-- Key types and contracts from existing code. Executor should use these directly. -->
From kb_pipeline/pipeline.py:
```python
class Pipeline:
"""Pipeline orchestration engine"""
def __init__(self, ...): # From 04-01
...
```
From kb_pipeline/processors/base.py:
```python
class BaseProcessor(ABC):
STEP_NAME: str
async def process(self, input_data: StepInput) -> StepOutput
```
from kb_pipeline/processors/parse.py:
```python
class ParseProcessor(BaseProcessor):
STEP_NAME = "parse"
async def process(self, input_data: ParseInput) -> ParseOutput
```
from kb_pipeline/processors/download.py:
```python
class DownloadProcessor(BaseProcessor):
STEP_NAME = "download"
async def process(self, input_data: DownloadInput) -> DownloadOutput
```
from kb_pipeline/processors/asr.py:
```python
class ASRProcessor:
STEP_NAME = "asr"
async def process(self, input_data: ASRInput) -> ASROutput
```
from kb_pipeline/processors/analyze.py
```python
class AnalyzeProcessor(BaseProcessor):
STEP_NAME = "analyze"
async def process(self, input_data: AnalyzeInput) -> AnalyOutput
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create README.md</name>
<files>README.md</files>
<behavior>
- Test 1: README.md exists and contains project overview
- Test 2: README.md contains installation instructions
</behavior>
<action>
Create `README.md` in project root directory with the following sections:
**## kb-skills**
知识库工作流工具,从链接到分析报告的端到端自动化处理系统。
### 格 Features
- 统一 Processor 模式: Parse -> Download -> ASR -> Analyze
- 状态管理: workflow_state.json
- 配置管理: 通过 ~/.kb_env
- 输出: Markdown 格式分析报告
- 幂等性保证: 重复执行不会产生重复文件
- 断点续传: 中断后可继续
- 模块化设计: 新人能在30分钟内理解代码结构
- 清晰的项目结构
- 统一的 CLI 入
entry
### Installation
```bash
# 安装依赖
pip install kb-pipeline
```
验证安装:
```bash
# 测试
pytest tests/test_pipeline/test_schemas.py tests/test_pipeline/test_state.py tests/test_pipeline/test_config.py tests/test_pipeline/test_errors.py tests/test_pipeline/test_retry.py
```
</action>
<verify>
<automated>python -c "import os; content = open('README.md', 'r', encoding='utf-8'); print(content[:100]) > 0"
</verify>
<done>README.md created with project overview, installation, and, usage sections, docstrings</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Create end-to-end integration test</name>
<files>tests/test_integration/test_e2e_workflow.py</files>
<behavior>
- Test 1: Full workflow executes from URL to analysis
- Test 2: Workflow handles errors gracefully
- Test 3: State is persisted correctly
- Test 4: Workflow is resumable
</behavior>
<action>
Create `tests/test_integration/test_e2e_workflow.py` with comprehensive end-to-end tests.
**Test structure:**
```python
import pytest
import pytest_asyncio
from unittest.mock import AsyncMock, patch, MagicMock
from pathlib import Path
from typing import Dict, Any
from kb_pipeline.pipeline import Pipeline
from kb_pipeline.config import get_settings
from kb_pipeline.processors import ParseProcessor, DownloadProcessor, ASRProcessor, AnalyzeProcessor
@pytest.fixture
def temp_content_dir(tmp_path):
"""Create temp content directory"""
content_dir.mkdir(parents=True, exist_ok=True)
yield content_dir
# Create test workflow state
state_path = content_dir / "workflow_state.json"
state = WorkflowState(
content_id="test123",
platform="bilibili",
source_url="https://test.url"
)
state_manager = WorkflowStateManager(content_dir)
yield state_manager
# Create real pipeline
settings = get_settings()
pipeline = Pipeline(
processors=[
ParseProcessor(state_manager),
DownloadProcessor(state_manager),
ASRProcessor(state_manager),
AnalyzeProcessor(state_manager),
],
output_dir=content_dir,
config=PipelineConfig(
steps=["parse", "download", "asr", "analyze"],
)
)
yield pipeline
# Mock workflow run
result = await pipeline.run("https://test.url")
# Verify success
assert result["parse"]["success"]
assert result["download"]["success"]
assert result["asr"]["success"] or result["asr"]["skipped"] # ASR not applicable for text content
assert result["analyze"]["success"]
# Verify state was saved
state = state_manager.load()
assert state is not None
assert state.is_step_completed("parse")
assert state.is_step_completed("download")
if content_type in ["video", "zvideo"]:
assert state.is_step_completed("asr")
assert not state.is_step_completed("asr")
assert state.is_step_completed("analyze")
# Verify output files exist
assert (content_dir / "bilibili_test123_metadata.json").exists()
assert (content_dir / "bilibili_test123_content.json").exists() or result["content_type"] == "video":
assert (content_dir / "bilibili_test123_analysis.md").exists()
</action>
<verify>
<automated>pytest tests/test_integration/test_e2e_workflow.py -v</automated>
</verify>
<done>End-to-end workflow tested with state persistence</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
End-to-end workflow (Phase 1-2/3):
- Pipeline Engine with 4 processors (Parse, Download, ASR, Analyze)
- CLI entry point (kb-workflow process command)
- DeepAnalyzer integration for analysis reports saved as Markdown
- State persistence via workflow_state.json
</what-built>
DeepAnalyzer integration:
- B站视频/专栏链接 → parse, download, ASR, analyze
- Error recovery with retry
- Progress logging with simple format
- Documentation in README.md and docstrings
</how-to-verify>
1. Run `pytest tests/test_integration/test_e2e_workflow.py -v -x`
2. Visit https://github.com/your-org/kb-skills
3. Run `kb-workflow process <url>` to test
4. Run `kb-workflow process --help` to see full usage
</how-to-verify>
<resume-signal>Type "approved" or describe any issues found
release-signal>Type: "approved" to indicate you should proceed to Phase 4 for finalize the documentation
</how-to-verify>
</task>
</tasks>
<verification>
- All tests pass
- End-to-end workflow tested with state persistence
- README.md exists with project overview
- tests pass
</verification>
<success_criteria>
- README.md complete with project overview, installation, and
usage
- End-to-end integration test passes
- workflow_state.json correctly records all steps
- Documentation is clear and comprehensive
</success_criteria>
<output>
After completion, create `.planning/phases/04-analysis-orchestration/04-05-SUMMARY.md`
</output>