Issues addressed: 1. Remove non-existent 04-06 from depends_on → [04-04] only 2. Add CLI 'process' subcommand implementation task (was missing) 3. Fix verify command syntax (pytest instead of malformed python -c) 4. Rewrite Task 2 action with proper test code structure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
phase: 04-analysis-orchestration plan: 05 type: execute wave: 5 depends_on: [04-04] files_modified:
- kb-workflow/kb_workflow.py
- tests/test_cli/test_process_command.py
- tests/test_integration/test_e2e_workflow.py autonomous: false requirements: [MNT-01] user_setup: []
must_haves: truths: - "CLI 'process' subcommand is available" - "kb-workflow process executes full pipeline" - "End-to-end workflow completes successfully" - "Analysis report is saved to content directory" - "workflow_state.json correctly records all steps" artifacts: - path: "kb-workflow/kb_workflow.py" provides: "CLI entry point with process subcommand" contains: "def process_command" - path: "tests/test_cli/test_process_command.py" provides: "CLI command tests" contains: "test_process_command" - path: "tests/test_integration/test_e2e_workflow.py" provides: "End-to-end integration test" contains: "test_e2e" key_links: - from: "kb-workflow/kb_workflow.py" to: "kb_pipeline" via: "import Pipeline" pattern: "from kb_pipeline" - from: "kb_pipeline/pipeline.py" to: "kb_pipeline/processors" via: "Pipeline.run()" pattern: "Pipeline("
Add CLI 'process' subcommand and create end-to-end integration tests.Purpose: Provide the single-command entry point kb-workflow process <url> as specified in CONTEXT.md, and verify the complete workflow works.
Output: CLI process subcommand in kb_workflow.py, CLI tests, E2E integration tests.
<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_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
from kb_pipeline/pipeline.py:
class Pipeline:
"""Pipeline orchestration engine"""
def __init__(
self,
processors: List[BaseProcessor],
config: Optional[PipelineConfig] = None,
output_dir: Optional[Path] = None,
): ...
async def run(self, url: str) -> PipelineOutput: ...
from kb_pipeline/schemas.py:
class PipelineInput(BaseModel):
url: str = Field(..., min_length=1)
output_dir: Optional[Path] = None
skip_completed: bool = Field(default=True)
class PipelineOutput(BaseModel):
success: bool
error_message: Optional[str] = None
results: Dict[str, Any] = Field(default_factory=dict)
content_dir: Optional[Path] = None
class PipelineConfig(BaseModel):
steps: List[str] = Field(default_factory=lambda: ["parse", "download", "asr", "analyze"])
output_dir: Optional[Path] = None
skip_completed: bool = Field(default=True)
max_retries: int = Field(default=3)
from kb_pipeline/processors/base.py:
class BaseProcessor(ABC):
STEP_NAME: str
async def process(self, input_data: StepInput) -> StepOutput
from kb-workflow/kb_workflow.py (existing CLI pattern):
def main():
parser = argparse.ArgumentParser(...)
parser.add_argument('--check', action='store_true', ...)
# ... existing arguments ...
Implementation requirements:
- Add subparser for
processcommand:
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Process subcommand
process_parser = subparsers.add_parser('process', help='Process URL through full pipeline')
process_parser.add_argument('url', type=str, help='URL to process')
process_parser.add_argument('--output-dir', '-o', type=str, help='Output directory for content')
process_parser.add_argument('--skip-completed', action='store_true', default=True, help='Skip completed steps')
process_parser.add_argument('--no-skip', action='store_false', dest='skip_completed', help='Re-run all steps')
- Add
process_commandfunction:
def process_command(url: str, output_dir: Optional[str] = None, skip_completed: bool = True) -> Dict[str, Any]:
"""Execute full pipeline for a URL"""
import asyncio
from kb_pipeline.pipeline import Pipeline
from kb_pipeline.processors import ParseProcessor, DownloadProcessor, ASRProcessor, AnalyzeProcessor
from kb_pipeline.schemas import PipelineConfig
from kb_pipeline.state import WorkflowStateManager
# Parse URL first to get platform and content_id
# ... implementation ...
# Create pipeline and run
async def run_pipeline():
# Initialize processors and pipeline
# ... implementation ...
result = asyncio.run(run_pipeline())
return result
-
Update
main()to dispatch toprocess_commandwhenargs.command == 'process' -
Create tests/test_cli/test_process_command.py with mocked Pipeline tests. pytest tests/test_cli/test_process_command.py -v -x CLI process subcommand implemented and tested
Test file structure:
"""End-to-end integration tests for the complete workflow."""
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.schemas import PipelineConfig, WorkflowState
from kb_pipeline.state import WorkflowStateManager
@pytest.fixture
def temp_content_dir(tmp_path):
"""Create temp content directory for testing."""
content_dir = tmp_path / "content"
content_dir.mkdir(parents=True, exist_ok=True)
yield content_dir
@pytest.fixture
def mock_state_manager(temp_content_dir):
"""Create mock workflow state manager."""
return WorkflowStateManager(temp_content_dir)
@pytest.fixture
def mock_processors():
"""Create mock processors for testing."""
processors = []
for step_name in ["parse", "download", "asr", "analyze"]:
processor = MagicMock()
processor.STEP_NAME = step_name
processor.process = AsyncMock(return_value=MagicMock(
success=True,
error_message=None,
))
processors.append(processor)
return processors
@pytest.mark.asyncio
async def test_full_workflow_executes_successfully(temp_content_dir, mock_processors):
"""Test that full workflow executes from URL to analysis."""
config = PipelineConfig(
steps=["parse", "download", "asr", "analyze"],
output_dir=temp_content_dir,
)
pipeline = Pipeline(
processors=mock_processors,
config=config,
output_dir=temp_content_dir,
)
result = await pipeline.run("https://www.bilibili.com/video/BV1test123")
assert result.success
for processor in mock_processors:
processor.process.assert_called_once()
@pytest.mark.asyncio
async def test_workflow_handles_errors_gracefully(temp_content_dir):
"""Test that workflow handles errors gracefully."""
# Create processor that fails
failing_processor = MagicMock()
failing_processor.STEP_NAME = "parse"
failing_processor.process = AsyncMock(return_value=MagicMock(
success=False,
error_message="Network error",
))
config = PipelineConfig(
steps=["parse"],
output_dir=temp_content_dir,
)
pipeline = Pipeline(
processors=[failing_processor],
config=config,
output_dir=temp_content_dir,
)
result = await pipeline.run("https://invalid.url")
assert not result.success
assert result.error_message is not None
@pytest.mark.asyncio
async def test_state_persisted_correctly(temp_content_dir, mock_state_manager):
"""Test that state is persisted correctly after each step."""
# ... test implementation
pass
@pytest.mark.asyncio
async def test_workflow_resumable(temp_content_dir):
"""Test that workflow can resume (skip completed steps)."""
# Create state with parse already completed
state_manager = WorkflowStateManager(temp_content_dir)
state = state_manager.get_or_create(
content_id="test123",
platform="bilibili",
source_url="https://test.url"
)
state.mark_step_completed("parse")
state_manager.save(state)
# Create processors
parse_processor = MagicMock()
parse_processor.STEP_NAME = "parse"
parse_processor.process = AsyncMock()
download_processor = MagicMock()
download_processor.STEP_NAME = "download"
download_processor.process = AsyncMock(return_value=MagicMock(success=True))
config = PipelineConfig(
steps=["parse", "download"],
output_dir=temp_content_dir,
skip_completed=True,
)
pipeline = Pipeline(
processors=[parse_processor, download_processor],
config=config,
output_dir=temp_content_dir,
)
# Run pipeline - parse should be skipped
result = await pipeline.run("https://www.bilibili.com/video/BV1test123")
# Parse should NOT be called (skipped)
parse_processor.process.assert_not_called()
# Download should be called
download_processor.process.assert_called_once()
<success_criteria>
- CLI
processsubcommand implemented in kb_workflow.py - CLI tests pass
- End-to-end integration test passes
- workflow_state.json correctly records all steps
- Documentation is clear and comprehensive </success_criteria>