|
| 1 | +# src/workflow_as_list/cli/init.py |
| 2 | +"""workflow init command - Initialize workflow-as-list integration. |
| 3 | +
|
| 4 | +REFERENCE: #42 - workflow init command for project onboarding |
| 5 | +""" |
| 6 | + |
| 7 | +import re |
| 8 | +from pathlib import Path |
| 9 | + |
| 10 | +import typer |
| 11 | +from rich.console import Console |
| 12 | + |
| 13 | +from ..constants import ensure_directories |
| 14 | + |
| 15 | +console = Console() |
| 16 | + |
| 17 | +# Templates moved to reduce file size |
| 18 | +WORKFLOW_DOCS_SECTION = """ |
| 19 | +## Workflow Automation |
| 20 | +
|
| 21 | +This project uses workflow-as-list for process automation. |
| 22 | +
|
| 23 | +NOTE: For `.workflow.list` files, use `workflow --help` or execute: |
| 24 | + workflow check/run/exec <workflow-name> |
| 25 | +
|
| 26 | +Quick start: |
| 27 | + workflow list # List available workflows |
| 28 | + workflow check <name> # Validate workflow |
| 29 | + workflow run <name> # Start execution |
| 30 | +""" |
| 31 | + |
| 32 | +CONFIG_TEMPLATE = """[workflow] |
| 33 | +project_name = {project_name} |
| 34 | +cache_dir = .imports |
| 35 | +state_dir = .workflow-as-list/state |
| 36 | +""" |
| 37 | + |
| 38 | +GITIGNORE_ENTRIES = """ |
| 39 | +# workflow-as-list cache |
| 40 | +.imports/ |
| 41 | +.workflow-as-list/ |
| 42 | +""" |
| 43 | + |
| 44 | +WORKFLOW_README_TEMPLATE = """# Project Workflows |
| 45 | +
|
| 46 | +Workflows manage {project_name} development. |
| 47 | +
|
| 48 | +Usage: |
| 49 | + workflow check workflow/<name> |
| 50 | + workflow run workflow/<name> |
| 51 | +
|
| 52 | +NOTE: Import caching is automatic (see .imports/) |
| 53 | +""" |
| 54 | + |
| 55 | + |
| 56 | +def print_output(type: str, message: str): |
| 57 | + """Print formatted output.""" |
| 58 | + styles = {"INFO": "blue", "SUCCESS": "green", "WARNING": "yellow", "ERROR": "red"} |
| 59 | + console.print(f"[{styles.get(type, 'white')}]{type}[/] {message}") |
| 60 | + |
| 61 | + |
| 62 | +def get_project_name() -> str: |
| 63 | + """Get project name from pyproject.toml or directory.""" |
| 64 | + pyproject = Path("pyproject.toml") |
| 65 | + if pyproject.exists(): |
| 66 | + match = re.search(r'name\s*=\s*"([^"]+)"', pyproject.read_text()) |
| 67 | + if match: |
| 68 | + return match.group(1) |
| 69 | + return Path.cwd().name |
| 70 | + |
| 71 | + |
| 72 | +def find_docs_file() -> Path | None: |
| 73 | + """Find AGENTS.md, README.md, or CONTRIBUTING.md.""" |
| 74 | + for name in ["AGENTS.md", "README.md", "CONTRIBUTING.md"]: |
| 75 | + path = Path(name) |
| 76 | + if path.exists(): |
| 77 | + return path |
| 78 | + return None |
| 79 | + |
| 80 | + |
| 81 | +def is_initialized() -> bool: |
| 82 | + """Check if already initialized.""" |
| 83 | + docs_file = find_docs_file() |
| 84 | + if docs_file and "workflow-as-list" in docs_file.read_text().lower(): |
| 85 | + return True |
| 86 | + return Path(".workflow-as-list/config.ini").exists() |
| 87 | + |
| 88 | + |
| 89 | +def update_docs(force: bool = False) -> bool: |
| 90 | + """Add workflow-as-list section to docs.""" |
| 91 | + docs_file = find_docs_file() |
| 92 | + if not docs_file: |
| 93 | + print_output("WARNING", "No AGENTS.md/README.md/CONTRIBUTING.md found") |
| 94 | + return False |
| 95 | + |
| 96 | + content = docs_file.read_text() |
| 97 | + if "workflow-as-list" in content.lower() and not force: |
| 98 | + print_output("INFO", f"Already initialized: {docs_file.name}") |
| 99 | + return False |
| 100 | + |
| 101 | + # Find insertion point |
| 102 | + insertion_point = len(content) |
| 103 | + for pattern, pos in [ |
| 104 | + (r"(## Getting Started\n)", "after"), |
| 105 | + (r"(## Development\n)", "before"), |
| 106 | + (r"(## Setup\n)", "after"), |
| 107 | + ]: |
| 108 | + match = re.search(pattern, content) |
| 109 | + if match: |
| 110 | + insertion_point = match.end() if pos == "after" else match.start() |
| 111 | + break |
| 112 | + |
| 113 | + docs_file.write_text( |
| 114 | + content[:insertion_point] + WORKFLOW_DOCS_SECTION + content[insertion_point:] |
| 115 | + ) |
| 116 | + print_output("SUCCESS", f"Updated {docs_file.name}") |
| 117 | + return True |
| 118 | + |
| 119 | + |
| 120 | +def create_config(force: bool = False) -> bool: |
| 121 | + """Create .workflow-as-list/config.ini.""" |
| 122 | + config_path = Path(".workflow-as-list/config.ini") |
| 123 | + if config_path.exists() and not force: |
| 124 | + print_output("INFO", f"Config exists: {config_path}") |
| 125 | + return False |
| 126 | + config_path.parent.mkdir(exist_ok=True) |
| 127 | + config_path.write_text(CONFIG_TEMPLATE.format(project_name=get_project_name())) |
| 128 | + print_output("SUCCESS", f"Created {config_path}") |
| 129 | + return True |
| 130 | + |
| 131 | + |
| 132 | +def update_gitignore(force: bool = False) -> bool: |
| 133 | + """Add cache dirs to .gitignore.""" |
| 134 | + gitignore_path = Path(".gitignore") |
| 135 | + if gitignore_path.exists(): |
| 136 | + content = gitignore_path.read_text() |
| 137 | + if ".imports/" in content and ".workflow-as-list/" in content and not force: |
| 138 | + print_output("INFO", ".gitignore already updated") |
| 139 | + return False |
| 140 | + gitignore_path.write_text(content.rstrip() + "\n\n" + GITIGNORE_ENTRIES) |
| 141 | + else: |
| 142 | + gitignore_path.write_text(GITIGNORE_ENTRIES) |
| 143 | + print_output("SUCCESS", f"Updated {gitignore_path.name}") |
| 144 | + return True |
| 145 | + |
| 146 | + |
| 147 | +def create_workflow_readme(force: bool = False) -> bool: |
| 148 | + """Create workflow/README.md if workflow/ exists.""" |
| 149 | + workflow_dir = Path("workflow") |
| 150 | + if not workflow_dir.is_dir(): |
| 151 | + return False |
| 152 | + readme_path = workflow_dir / "README.md" |
| 153 | + if readme_path.exists() and not force: |
| 154 | + print_output("INFO", "workflow/README.md exists") |
| 155 | + return False |
| 156 | + readme_path.write_text( |
| 157 | + WORKFLOW_README_TEMPLATE.format(project_name=get_project_name()) |
| 158 | + ) |
| 159 | + print_output("SUCCESS", "Created workflow/README.md") |
| 160 | + return True |
| 161 | + |
| 162 | + |
| 163 | +def init( |
| 164 | + docs_only: bool = typer.Option( |
| 165 | + False, "--docs-only", help="Only update documentation" |
| 166 | + ), |
| 167 | + config_only: bool = typer.Option( |
| 168 | + False, "--config-only", help="Only create config files" |
| 169 | + ), |
| 170 | + force: bool = typer.Option(False, "--force", help="Overwrite existing files"), |
| 171 | +): |
| 172 | + """Initialize workflow-as-list integration for a project. |
| 173 | +
|
| 174 | + Adds workflow-as-list documentation to AGENTS.md or README.md, |
| 175 | + creates configuration files, and updates .gitignore. |
| 176 | +
|
| 177 | + Example: |
| 178 | + workflow init |
| 179 | + workflow init --docs-only |
| 180 | + workflow init --force |
| 181 | + """ |
| 182 | + # Ensure directories exist |
| 183 | + ensure_directories() |
| 184 | + |
| 185 | + # Check if running in a project directory |
| 186 | + docs_file = find_docs_file() |
| 187 | + if not docs_file and not (docs_only or config_only): |
| 188 | + print_output("ERROR", "No AGENTS.md, README.md, or CONTRIBUTING.md found") |
| 189 | + print_output("ERROR", "Current directory does not appear to be a project") |
| 190 | + print_output("INFO", "Create a README.md or AGENTS.md file first") |
| 191 | + raise typer.Exit(1) |
| 192 | + |
| 193 | + # Check if already initialized |
| 194 | + if is_initialized() and not force: |
| 195 | + print_output("WARNING", "Project already initialized with workflow-as-list") |
| 196 | + print_output("INFO", "Use --force to re-initialize") |
| 197 | + |
| 198 | + print_output("INFO", "Initializing workflow-as-list integration...") |
| 199 | + |
| 200 | + success = False |
| 201 | + |
| 202 | + if docs_only: |
| 203 | + success = update_docs(force) |
| 204 | + elif config_only: |
| 205 | + create_config(force) |
| 206 | + update_gitignore(force) |
| 207 | + create_workflow_readme(force) |
| 208 | + success = True |
| 209 | + else: |
| 210 | + # Full initialization |
| 211 | + success = update_docs(force) |
| 212 | + create_config(force) |
| 213 | + update_gitignore(force) |
| 214 | + create_workflow_readme(force) |
| 215 | + |
| 216 | + if success: |
| 217 | + print_output( |
| 218 | + "SUCCESS", "Project initialized. Run 'workflow list' to see workflows." |
| 219 | + ) |
| 220 | + else: |
| 221 | + print_output("WARNING", "Initialization completed with warnings") |
0 commit comments