erk 0.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erk/__init__.py +12 -0
- erk/__main__.py +6 -0
- erk/agent_docs/__init__.py +5 -0
- erk/agent_docs/models.py +123 -0
- erk/agent_docs/operations.py +666 -0
- erk/artifacts/__init__.py +5 -0
- erk/artifacts/artifact_health.py +623 -0
- erk/artifacts/detection.py +16 -0
- erk/artifacts/discovery.py +343 -0
- erk/artifacts/models.py +63 -0
- erk/artifacts/staleness.py +56 -0
- erk/artifacts/state.py +100 -0
- erk/artifacts/sync.py +624 -0
- erk/cli/__init__.py +0 -0
- erk/cli/activation.py +132 -0
- erk/cli/alias.py +53 -0
- erk/cli/cli.py +221 -0
- erk/cli/commands/__init__.py +0 -0
- erk/cli/commands/admin.py +153 -0
- erk/cli/commands/artifact/__init__.py +1 -0
- erk/cli/commands/artifact/check.py +260 -0
- erk/cli/commands/artifact/group.py +31 -0
- erk/cli/commands/artifact/list_cmd.py +89 -0
- erk/cli/commands/artifact/show.py +62 -0
- erk/cli/commands/artifact/sync_cmd.py +39 -0
- erk/cli/commands/branch/__init__.py +26 -0
- erk/cli/commands/branch/assign_cmd.py +152 -0
- erk/cli/commands/branch/checkout_cmd.py +357 -0
- erk/cli/commands/branch/create_cmd.py +161 -0
- erk/cli/commands/branch/list_cmd.py +82 -0
- erk/cli/commands/branch/unassign_cmd.py +197 -0
- erk/cli/commands/cc/__init__.py +15 -0
- erk/cli/commands/cc/jsonl_cmd.py +20 -0
- erk/cli/commands/cc/session/AGENTS.md +30 -0
- erk/cli/commands/cc/session/CLAUDE.md +1 -0
- erk/cli/commands/cc/session/__init__.py +15 -0
- erk/cli/commands/cc/session/list_cmd.py +167 -0
- erk/cli/commands/cc/session/show_cmd.py +175 -0
- erk/cli/commands/completion.py +89 -0
- erk/cli/commands/completions.py +165 -0
- erk/cli/commands/config.py +327 -0
- erk/cli/commands/docs/__init__.py +1 -0
- erk/cli/commands/docs/group.py +16 -0
- erk/cli/commands/docs/sync.py +121 -0
- erk/cli/commands/docs/validate.py +102 -0
- erk/cli/commands/doctor.py +243 -0
- erk/cli/commands/down.py +171 -0
- erk/cli/commands/exec/__init__.py +1 -0
- erk/cli/commands/exec/group.py +164 -0
- erk/cli/commands/exec/scripts/AGENTS.md +79 -0
- erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
- erk/cli/commands/exec/scripts/__init__.py +5 -0
- erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
- erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
- erk/cli/commands/exec/scripts/check_impl.py +152 -0
- erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
- erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
- erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
- erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
- erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
- erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
- erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
- erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
- erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
- erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
- erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
- erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
- erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
- erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
- erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
- erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
- erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
- erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
- erk/cli/commands/exec/scripts/impl_init.py +158 -0
- erk/cli/commands/exec/scripts/impl_signal.py +375 -0
- erk/cli/commands/exec/scripts/impl_verify.py +49 -0
- erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
- erk/cli/commands/exec/scripts/list_sessions.py +296 -0
- erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
- erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
- erk/cli/commands/exec/scripts/marker.py +163 -0
- erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
- erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
- erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
- erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
- erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
- erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
- erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
- erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
- erk/cli/commands/exec/scripts/quick_submit.py +32 -0
- erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
- erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
- erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
- erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
- erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
- erk/cli/commands/exec/scripts/slot_objective.py +102 -0
- erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
- erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
- erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
- erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
- erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
- erk/cli/commands/implement.py +695 -0
- erk/cli/commands/implement_shared.py +649 -0
- erk/cli/commands/info/__init__.py +14 -0
- erk/cli/commands/info/release_notes_cmd.py +128 -0
- erk/cli/commands/init.py +801 -0
- erk/cli/commands/land_cmd.py +690 -0
- erk/cli/commands/log_cmd.py +137 -0
- erk/cli/commands/md/__init__.py +5 -0
- erk/cli/commands/md/check.py +118 -0
- erk/cli/commands/md/group.py +14 -0
- erk/cli/commands/navigation_helpers.py +430 -0
- erk/cli/commands/objective/__init__.py +16 -0
- erk/cli/commands/objective/list_cmd.py +47 -0
- erk/cli/commands/objective_helpers.py +132 -0
- erk/cli/commands/plan/__init__.py +32 -0
- erk/cli/commands/plan/check_cmd.py +174 -0
- erk/cli/commands/plan/close_cmd.py +69 -0
- erk/cli/commands/plan/create_cmd.py +120 -0
- erk/cli/commands/plan/docs/__init__.py +18 -0
- erk/cli/commands/plan/docs/extract_cmd.py +53 -0
- erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
- erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
- erk/cli/commands/plan/extraction/__init__.py +16 -0
- erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
- erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
- erk/cli/commands/plan/get.py +71 -0
- erk/cli/commands/plan/list_cmd.py +754 -0
- erk/cli/commands/plan/log_cmd.py +440 -0
- erk/cli/commands/plan/start_cmd.py +459 -0
- erk/cli/commands/planner/__init__.py +40 -0
- erk/cli/commands/planner/configure_cmd.py +73 -0
- erk/cli/commands/planner/connect_cmd.py +96 -0
- erk/cli/commands/planner/create_cmd.py +148 -0
- erk/cli/commands/planner/list_cmd.py +51 -0
- erk/cli/commands/planner/register_cmd.py +105 -0
- erk/cli/commands/planner/set_default_cmd.py +23 -0
- erk/cli/commands/planner/unregister_cmd.py +43 -0
- erk/cli/commands/pr/__init__.py +23 -0
- erk/cli/commands/pr/check_cmd.py +112 -0
- erk/cli/commands/pr/checkout_cmd.py +165 -0
- erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
- erk/cli/commands/pr/parse_pr_reference.py +10 -0
- erk/cli/commands/pr/submit_cmd.py +360 -0
- erk/cli/commands/pr/sync_cmd.py +181 -0
- erk/cli/commands/prepare_cwd_recovery.py +60 -0
- erk/cli/commands/project/__init__.py +16 -0
- erk/cli/commands/project/init_cmd.py +91 -0
- erk/cli/commands/run/__init__.py +17 -0
- erk/cli/commands/run/list_cmd.py +189 -0
- erk/cli/commands/run/logs_cmd.py +54 -0
- erk/cli/commands/run/shared.py +19 -0
- erk/cli/commands/shell_integration.py +29 -0
- erk/cli/commands/slot/__init__.py +23 -0
- erk/cli/commands/slot/check_cmd.py +277 -0
- erk/cli/commands/slot/common.py +314 -0
- erk/cli/commands/slot/init_pool_cmd.py +157 -0
- erk/cli/commands/slot/list_cmd.py +228 -0
- erk/cli/commands/slot/repair_cmd.py +190 -0
- erk/cli/commands/stack/__init__.py +23 -0
- erk/cli/commands/stack/consolidate_cmd.py +470 -0
- erk/cli/commands/stack/list_cmd.py +79 -0
- erk/cli/commands/stack/move_cmd.py +309 -0
- erk/cli/commands/stack/split_old/README.md +64 -0
- erk/cli/commands/stack/split_old/__init__.py +5 -0
- erk/cli/commands/stack/split_old/command.py +233 -0
- erk/cli/commands/stack/split_old/display.py +116 -0
- erk/cli/commands/stack/split_old/plan.py +216 -0
- erk/cli/commands/status.py +58 -0
- erk/cli/commands/submit.py +768 -0
- erk/cli/commands/up.py +154 -0
- erk/cli/commands/upgrade.py +82 -0
- erk/cli/commands/wt/__init__.py +29 -0
- erk/cli/commands/wt/checkout_cmd.py +110 -0
- erk/cli/commands/wt/create_cmd.py +998 -0
- erk/cli/commands/wt/current_cmd.py +35 -0
- erk/cli/commands/wt/delete_cmd.py +573 -0
- erk/cli/commands/wt/list_cmd.py +332 -0
- erk/cli/commands/wt/rename_cmd.py +66 -0
- erk/cli/config.py +242 -0
- erk/cli/constants.py +29 -0
- erk/cli/core.py +65 -0
- erk/cli/debug.py +9 -0
- erk/cli/ensure-conversion-tasks.md +288 -0
- erk/cli/ensure.py +628 -0
- erk/cli/github_parsing.py +96 -0
- erk/cli/graphite.py +81 -0
- erk/cli/graphite_command.py +80 -0
- erk/cli/help_formatter.py +345 -0
- erk/cli/output.py +361 -0
- erk/cli/presets/dagster.toml +12 -0
- erk/cli/presets/generic.toml +12 -0
- erk/cli/prompt_hooks_templates/README.md +68 -0
- erk/cli/script_output.py +32 -0
- erk/cli/shell_integration/bash_wrapper.sh +32 -0
- erk/cli/shell_integration/fish_wrapper.fish +39 -0
- erk/cli/shell_integration/handler.py +338 -0
- erk/cli/shell_integration/zsh_wrapper.sh +32 -0
- erk/cli/shell_utils.py +171 -0
- erk/cli/subprocess_utils.py +92 -0
- erk/cli/uvx_detection.py +59 -0
- erk/core/__init__.py +0 -0
- erk/core/claude_executor.py +511 -0
- erk/core/claude_settings.py +317 -0
- erk/core/command_log.py +406 -0
- erk/core/commit_message_generator.py +234 -0
- erk/core/completion.py +10 -0
- erk/core/consolidation_utils.py +177 -0
- erk/core/context.py +570 -0
- erk/core/display/__init__.py +4 -0
- erk/core/display/abc.py +24 -0
- erk/core/display/real.py +30 -0
- erk/core/display_utils.py +526 -0
- erk/core/file_utils.py +87 -0
- erk/core/health_checks.py +1315 -0
- erk/core/health_checks_dogfooder/__init__.py +85 -0
- erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
- erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
- erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
- erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
- erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
- erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
- erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
- erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
- erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
- erk/core/implementation_queue/__init__.py +1 -0
- erk/core/implementation_queue/github/__init__.py +8 -0
- erk/core/implementation_queue/github/abc.py +7 -0
- erk/core/implementation_queue/github/noop.py +38 -0
- erk/core/implementation_queue/github/printing.py +43 -0
- erk/core/implementation_queue/github/real.py +119 -0
- erk/core/init_utils.py +227 -0
- erk/core/output_filter.py +338 -0
- erk/core/plan_store/__init__.py +6 -0
- erk/core/planner/__init__.py +1 -0
- erk/core/planner/registry_abc.py +8 -0
- erk/core/planner/registry_fake.py +129 -0
- erk/core/planner/registry_real.py +195 -0
- erk/core/planner/types.py +7 -0
- erk/core/pr_utils.py +30 -0
- erk/core/release_notes.py +263 -0
- erk/core/repo_discovery.py +126 -0
- erk/core/script_writer.py +41 -0
- erk/core/services/__init__.py +1 -0
- erk/core/services/plan_list_service.py +94 -0
- erk/core/shell.py +51 -0
- erk/core/user_feedback.py +11 -0
- erk/core/version_check.py +55 -0
- erk/core/workflow_display.py +75 -0
- erk/core/worktree_pool.py +190 -0
- erk/core/worktree_utils.py +300 -0
- erk/data/CHANGELOG.md +438 -0
- erk/data/__init__.py +1 -0
- erk/data/claude/agents/devrun.md +180 -0
- erk/data/claude/commands/erk/__init__.py +0 -0
- erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
- erk/data/claude/commands/erk/fix-conflicts.md +25 -0
- erk/data/claude/commands/erk/git-pr-push.md +345 -0
- erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
- erk/data/claude/commands/erk/land.md +193 -0
- erk/data/claude/commands/erk/objective-create.md +370 -0
- erk/data/claude/commands/erk/objective-list.md +34 -0
- erk/data/claude/commands/erk/objective-next-plan.md +220 -0
- erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
- erk/data/claude/commands/erk/plan-implement.md +202 -0
- erk/data/claude/commands/erk/plan-save.md +45 -0
- erk/data/claude/commands/erk/plan-submit.md +39 -0
- erk/data/claude/commands/erk/pr-address.md +367 -0
- erk/data/claude/commands/erk/pr-submit.md +58 -0
- erk/data/claude/skills/dignified-python/SKILL.md +48 -0
- erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
- erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
- erk/data/claude/skills/dignified-python/subprocess.md +99 -0
- erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
- erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
- erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
- erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
- erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
- erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
- erk/data/claude/skills/learned-docs/SKILL.md +362 -0
- erk/data/github/actions/setup-claude-erk/action.yml +11 -0
- erk/data/github/prompts/dignified-python-review.md +125 -0
- erk/data/github/workflows/dignified-python-review.yml +61 -0
- erk/data/github/workflows/erk-impl.yml +251 -0
- erk/hooks/__init__.py +1 -0
- erk/hooks/decorators.py +319 -0
- erk/status/__init__.py +8 -0
- erk/status/collectors/__init__.py +9 -0
- erk/status/collectors/base.py +52 -0
- erk/status/collectors/git.py +76 -0
- erk/status/collectors/github.py +81 -0
- erk/status/collectors/graphite.py +80 -0
- erk/status/collectors/impl.py +145 -0
- erk/status/models/__init__.py +4 -0
- erk/status/models/status_data.py +404 -0
- erk/status/orchestrator.py +169 -0
- erk/status/renderers/__init__.py +5 -0
- erk/status/renderers/simple.py +322 -0
- erk/tui/AGENTS.md +193 -0
- erk/tui/CLAUDE.md +1 -0
- erk/tui/__init__.py +1 -0
- erk/tui/app.py +1404 -0
- erk/tui/commands/__init__.py +1 -0
- erk/tui/commands/executor.py +66 -0
- erk/tui/commands/provider.py +165 -0
- erk/tui/commands/real_executor.py +63 -0
- erk/tui/commands/registry.py +121 -0
- erk/tui/commands/types.py +36 -0
- erk/tui/data/__init__.py +1 -0
- erk/tui/data/provider.py +492 -0
- erk/tui/data/types.py +104 -0
- erk/tui/filtering/__init__.py +1 -0
- erk/tui/filtering/logic.py +43 -0
- erk/tui/filtering/types.py +55 -0
- erk/tui/jsonl_viewer/__init__.py +1 -0
- erk/tui/jsonl_viewer/app.py +61 -0
- erk/tui/jsonl_viewer/models.py +208 -0
- erk/tui/jsonl_viewer/widgets.py +204 -0
- erk/tui/sorting/__init__.py +6 -0
- erk/tui/sorting/logic.py +55 -0
- erk/tui/sorting/types.py +68 -0
- erk/tui/styles/dash.tcss +95 -0
- erk/tui/widgets/__init__.py +1 -0
- erk/tui/widgets/command_output.py +112 -0
- erk/tui/widgets/plan_table.py +276 -0
- erk/tui/widgets/status_bar.py +116 -0
- erk-0.4.5.dist-info/METADATA +376 -0
- erk-0.4.5.dist-info/RECORD +331 -0
- erk-0.4.5.dist-info/WHEEL +4 -0
- erk-0.4.5.dist-info/entry_points.txt +2 -0
- erk-0.4.5.dist-info/licenses/LICENSE.md +3 -0
erk/cli/core.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from erk.cli.ensure import Ensure
|
|
4
|
+
from erk.core.context import ErkContext
|
|
5
|
+
from erk.core.repo_discovery import RepoContext, discover_repo_or_sentinel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def discover_repo_context(ctx: ErkContext, start: Path) -> RepoContext:
|
|
9
|
+
"""Walk up from `start` to find a directory containing `.git`.
|
|
10
|
+
|
|
11
|
+
Returns a RepoContext pointing to the repo root and the global worktrees directory
|
|
12
|
+
for this repository.
|
|
13
|
+
Raises FileNotFoundError if not inside a git repo.
|
|
14
|
+
|
|
15
|
+
Note: Properly handles git worktrees by finding the main repository root,
|
|
16
|
+
not the worktree's .git file.
|
|
17
|
+
"""
|
|
18
|
+
if ctx.global_config is None:
|
|
19
|
+
raise FileNotFoundError("Global config not found. Run 'erk init' to create it.")
|
|
20
|
+
|
|
21
|
+
result = discover_repo_or_sentinel(start, ctx.global_config.erk_root, ctx.git)
|
|
22
|
+
if isinstance(result, RepoContext):
|
|
23
|
+
return result
|
|
24
|
+
raise FileNotFoundError(result.message)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def worktree_path_for(worktrees_dir: Path, name: str) -> Path:
|
|
28
|
+
"""Return the absolute path for a named worktree within worktrees directory.
|
|
29
|
+
|
|
30
|
+
Note: Does not handle 'root' as a special case. Commands that support
|
|
31
|
+
'root' must check for it explicitly and use repo.root directly.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
worktrees_dir: The directory containing all worktrees for this repo
|
|
35
|
+
name: The worktree name (e.g., 'feature-a')
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Absolute path to the worktree (e.g., ~/.erk/repos/myrepo/worktrees/feature-a/)
|
|
39
|
+
"""
|
|
40
|
+
return (worktrees_dir / name).resolve()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_worktree_name_for_deletion(name: str) -> None:
|
|
44
|
+
"""Validate that a worktree name is safe for deletion.
|
|
45
|
+
|
|
46
|
+
Rejects:
|
|
47
|
+
- Empty strings
|
|
48
|
+
- `.` or `..` (current/parent directory references)
|
|
49
|
+
- `root` (explicit root worktree name)
|
|
50
|
+
- Names starting with `/` (absolute paths)
|
|
51
|
+
- Names containing `/` (path separators)
|
|
52
|
+
|
|
53
|
+
Raises SystemExit(1) with error message if validation fails.
|
|
54
|
+
"""
|
|
55
|
+
Ensure.not_empty(name.strip() if name else "", "Worktree name cannot be empty")
|
|
56
|
+
Ensure.invariant(
|
|
57
|
+
name not in (".", ".."),
|
|
58
|
+
f"Cannot delete '{name}' - directory references not allowed",
|
|
59
|
+
)
|
|
60
|
+
Ensure.invariant(name != "root", "Cannot delete 'root' - root worktree name not allowed")
|
|
61
|
+
Ensure.invariant(
|
|
62
|
+
not name.startswith("/"),
|
|
63
|
+
f"Cannot delete '{name}' - absolute paths not allowed",
|
|
64
|
+
)
|
|
65
|
+
Ensure.invariant("/" not in name, f"Cannot delete '{name}' - path separators not allowed")
|
erk/cli/debug.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Debug logging utilities for erk.
|
|
2
|
+
|
|
3
|
+
DEPRECATED: Import from erk_shared.debug instead.
|
|
4
|
+
This module is a re-export shim for backwards compatibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Re-export for backwards compatibility (explicit re-export syntax per PEP 484)
|
|
8
|
+
from erk_shared.debug import debug_log as debug_log
|
|
9
|
+
from erk_shared.debug import is_debug as is_debug
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Ensure Class Conversion Tasks
|
|
2
|
+
|
|
3
|
+
**Purpose**: Track conversion of manual error checking to use the centralized Ensure class.
|
|
4
|
+
**Status**: IN_PROGRESS
|
|
5
|
+
**Last Updated**: 2025-11-26
|
|
6
|
+
|
|
7
|
+
## Status Legend
|
|
8
|
+
|
|
9
|
+
- [ ] Not started
|
|
10
|
+
- [WIP] Work in progress
|
|
11
|
+
- [x] Completed
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Phase 1: New Ensure Methods
|
|
16
|
+
|
|
17
|
+
### Priority 1: High-Value Methods (8+ uses)
|
|
18
|
+
|
|
19
|
+
#### Task 1.1: Implement Ensure.mutually_exclusive_flags()
|
|
20
|
+
|
|
21
|
+
- [ ] Write method implementation
|
|
22
|
+
- [ ] Add unit tests (success case, multiple flags set, custom message)
|
|
23
|
+
- [ ] Document in method docstring
|
|
24
|
+
- **Usage**: 8 occurrences (wt/create, stack/consolidate, stack/move, stack/split)
|
|
25
|
+
- **Signature**: `mutually_exclusive_flags(flags: dict[str, bool | Any], error_message: str | None = None) -> None`
|
|
26
|
+
|
|
27
|
+
#### Task 1.2: Implement Ensure.not_detached_head()
|
|
28
|
+
|
|
29
|
+
- [ ] Write method implementation with type narrowing
|
|
30
|
+
- [ ] Add unit tests (on branch, detached HEAD, custom operation name)
|
|
31
|
+
- [ ] Document in method docstring
|
|
32
|
+
- **Usage**: 4 occurrences (stack/consolidate, stack/move, wt/delete)
|
|
33
|
+
- **Signature**: `not_detached_head(ctx: ErkContext, branch: str | None, operation_name: str) -> str`
|
|
34
|
+
- **Returns**: Narrowed type `str` (from `str | None`)
|
|
35
|
+
|
|
36
|
+
#### Task 1.3: Implement Ensure.clean_working_tree()
|
|
37
|
+
|
|
38
|
+
- [ ] Write method implementation with allow_force parameter
|
|
39
|
+
- [ ] Add unit tests (clean tree, uncommitted changes, force override)
|
|
40
|
+
- [ ] Document in method docstring
|
|
41
|
+
- **Usage**: 6 occurrences (navigation_helpers, stack/move, stack/split)
|
|
42
|
+
- **Signature**: `clean_working_tree(ctx: ErkContext, path: Path, operation_name: str | None = None, allow_force: bool = False) -> None`
|
|
43
|
+
|
|
44
|
+
#### Task 1.4: Implement Ensure.graphite_enabled()
|
|
45
|
+
|
|
46
|
+
- [ ] Write method implementation
|
|
47
|
+
- [ ] Add unit tests (enabled, disabled)
|
|
48
|
+
- [ ] Document in method docstring
|
|
49
|
+
- **Usage**: 3 occurrences (navigation_helpers, wt/delete)
|
|
50
|
+
- **Signature**: `graphite_enabled(ctx: ErkContext) -> None`
|
|
51
|
+
- **Error**: "This command requires Graphite to be enabled. Run 'erk config set use_graphite true'"
|
|
52
|
+
|
|
53
|
+
### Priority 2: Medium-Value Methods (2-4 uses)
|
|
54
|
+
|
|
55
|
+
#### Task 1.5: Implement Ensure.branch_tracked_by_graphite()
|
|
56
|
+
|
|
57
|
+
- [ ] Write method implementation with type narrowing
|
|
58
|
+
- [ ] Add unit tests (tracked branch, untracked branch)
|
|
59
|
+
- [ ] Document in method docstring
|
|
60
|
+
- **Usage**: 2 occurrences (stack/consolidate)
|
|
61
|
+
- **Signature**: `branch_tracked_by_graphite(ctx: ErkContext, repo_root: Path, branch: str) -> list[str]`
|
|
62
|
+
- **Returns**: Stack branches (narrows from `list[str] | None`)
|
|
63
|
+
|
|
64
|
+
#### Task 1.6: Implement Ensure.in_repo()
|
|
65
|
+
|
|
66
|
+
- [ ] Write method implementation
|
|
67
|
+
- [ ] Add unit tests (in repo, NoRepoSentinel)
|
|
68
|
+
- [ ] Document in method docstring
|
|
69
|
+
- **Usage**: 4 occurrences (context.py, pr/checkout, config)
|
|
70
|
+
- **Signature**: `in_repo(ctx: ErkContext) -> None`
|
|
71
|
+
|
|
72
|
+
#### Task 1.7: Implement Ensure.global_config_exists()
|
|
73
|
+
|
|
74
|
+
- [ ] Write method implementation with type narrowing
|
|
75
|
+
- [ ] Add unit tests (config exists, config None)
|
|
76
|
+
- [ ] Document in method docstring
|
|
77
|
+
- **Usage**: 4 occurrences (init, config, core)
|
|
78
|
+
- **Signature**: `global_config_exists(ctx: ErkContext) -> GlobalConfig`
|
|
79
|
+
|
|
80
|
+
#### Task 1.8: Implement Ensure.not_trunk_branch()
|
|
81
|
+
|
|
82
|
+
- [ ] Write method implementation
|
|
83
|
+
- [ ] Add unit tests (trunk branch, non-trunk branch)
|
|
84
|
+
- [ ] Document in method docstring
|
|
85
|
+
- **Usage**: 1-2 occurrences (wt/create)
|
|
86
|
+
- **Signature**: `not_trunk_branch(ctx: ErkContext, repo_root: Path, branch: str) -> None`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Phase 2: File Conversions
|
|
91
|
+
|
|
92
|
+
### High Priority (20+ patterns each)
|
|
93
|
+
|
|
94
|
+
#### Task 2.1: Convert wt/create_cmd.py (30+ patterns)
|
|
95
|
+
|
|
96
|
+
- [ ] Lines 556-561: Replace manual check with `Ensure.mutually_exclusive_flags()` for --from-\* flags
|
|
97
|
+
- [ ] Lines 564-566: Replace manual check with `Ensure.mutually_exclusive_flags()` for --json/--script
|
|
98
|
+
- [ ] Lines 574-582: Replace manual check with `Ensure.mutually_exclusive_flags()` for --copy-plan
|
|
99
|
+
- [ ] Lines 101-108: Replace manual check with `Ensure.not_trunk_branch()`
|
|
100
|
+
- [ ] Lines 276: Replace None check with `Ensure.not_detached_head()` (already uses Ensure.not_none at line 610)
|
|
101
|
+
- [ ] Lines 587-594: Replace .exists()/.is_dir() checks with `Ensure.path_is_dir()` for .impl directory
|
|
102
|
+
- [ ] Lines 610-612: Keep as-is (already uses `Ensure.not_none()` correctly)
|
|
103
|
+
- [ ] Run tests: `uv run pytest tests/commands/workspace/test_create.py`
|
|
104
|
+
- [ ] Verify no regressions
|
|
105
|
+
|
|
106
|
+
#### Task 2.2: Convert stack/consolidate_cmd.py (15+ patterns)
|
|
107
|
+
|
|
108
|
+
- [ ] Lines 153-159: Replace manual check with `Ensure.mutually_exclusive_flags({"--down": down, "BRANCH": branch is not None})`
|
|
109
|
+
- [ ] Lines 165-168: Replace None check with `Ensure.not_detached_head(ctx, current_branch, "consolidate")`
|
|
110
|
+
- [ ] Lines 176-181: Replace None check with `stack_branches = Ensure.branch_tracked_by_graphite(ctx, repo.root, current_branch)`
|
|
111
|
+
- [ ] Lines 184-193: Keep as-is (nice formatted stack display, use `Ensure.invariant()` wrapper)
|
|
112
|
+
- [ ] Lines 202-212: Keep as-is or use `Ensure.invariant(name not in existing_names, msg)`
|
|
113
|
+
- [ ] Lines 231-241: Consider refactoring to `Ensure.clean_working_tree()` for each worktree in loop
|
|
114
|
+
- [ ] Run tests: `uv run pytest tests/commands/stack/`
|
|
115
|
+
- [ ] Verify no regressions
|
|
116
|
+
|
|
117
|
+
#### Task 2.3: Convert implement.py (25+ patterns)
|
|
118
|
+
|
|
119
|
+
- [ ] Lines 373-375: Replace .exists() check with `Ensure.path_exists(ctx, plan_file, ...)`
|
|
120
|
+
- [ ] Lines 632: Use `Ensure.not_none()` for wt_path
|
|
121
|
+
- [ ] Lines 708: Use `Ensure.not_none()` for target_info.issue_number
|
|
122
|
+
- [ ] Lines 866: Use `Ensure.not_none()` for wt_path
|
|
123
|
+
- [ ] Audit all None checks for conversion opportunities
|
|
124
|
+
- [ ] Run tests: `uv run pytest tests/commands/test_implement.py` (if exists)
|
|
125
|
+
- [ ] Verify no regressions
|
|
126
|
+
|
|
127
|
+
### Medium Priority (10-20 patterns each)
|
|
128
|
+
|
|
129
|
+
#### Task 2.4: Convert navigation_helpers.py
|
|
130
|
+
|
|
131
|
+
- [ ] Lines 14-28: Delete `ensure_graphite_enabled()` function after converting callers
|
|
132
|
+
- [ ] Lines 31-42: Delete `check_clean_working_tree()` function after converting callers
|
|
133
|
+
- [ ] Lines 45-68: Delete `verify_pr_merged()` function OR convert to `Ensure.pr_merged()` if implementing
|
|
134
|
+
- [ ] Lines 212-214: Use `Ensure.not_none()` in navigate_upstack
|
|
135
|
+
- [ ] Lines 246-254: Use `Ensure.not_none()` in navigate_downstack (multiple checks)
|
|
136
|
+
- [ ] Update up.py and down.py to call Ensure methods directly
|
|
137
|
+
- [ ] Run tests: `uv run pytest tests/commands/navigation/`
|
|
138
|
+
- [ ] Verify no regressions
|
|
139
|
+
|
|
140
|
+
#### Task 2.5: Convert stack/move_cmd.py
|
|
141
|
+
|
|
142
|
+
- [ ] Lines 59-61: Replace with `Ensure.mutually_exclusive_flags()`
|
|
143
|
+
- [ ] Lines 115: Replace with `Ensure.not_detached_head()`
|
|
144
|
+
- [ ] Lines 119-124: Replace with `Ensure.clean_working_tree(ctx, source_wt, "move", allow_force=force)`
|
|
145
|
+
- [ ] Lines 138: Replace with `Ensure.clean_working_tree(ctx, target_wt, "move", allow_force=force)`
|
|
146
|
+
- [ ] Lines 186: Replace with `Ensure.clean_working_tree()`
|
|
147
|
+
- [ ] Run tests: `uv run pytest tests/commands/stack/test_move.py`
|
|
148
|
+
- [ ] Verify no regressions
|
|
149
|
+
|
|
150
|
+
#### Task 2.6: Convert stack/split_old/command.py
|
|
151
|
+
|
|
152
|
+
- [ ] Lines 32-37: Replace with `Ensure.mutually_exclusive_flags({"--up": up, "--down": down})`
|
|
153
|
+
- [ ] Lines 64-67: Replace with `Ensure.clean_working_tree(ctx, current_worktree, "split")`
|
|
154
|
+
- [ ] Run tests: `uv run pytest tests/commands/stack/test_split.py`
|
|
155
|
+
- [ ] Verify no regressions
|
|
156
|
+
|
|
157
|
+
### Lower Priority (5-10 patterns each)
|
|
158
|
+
|
|
159
|
+
#### Task 2.7: Convert wt/delete_cmd.py
|
|
160
|
+
|
|
161
|
+
- [ ] Lines 132-137: Replace with `Ensure.graphite_enabled(ctx)`
|
|
162
|
+
- [ ] Lines 143-147: Replace with `Ensure.not_detached_head()`
|
|
163
|
+
- [ ] Run tests: `uv run pytest tests/commands/workspace/test_delete.py`
|
|
164
|
+
- [ ] Verify no regressions
|
|
165
|
+
|
|
166
|
+
#### Task 2.8: Convert commands/config.py
|
|
167
|
+
|
|
168
|
+
- [ ] Lines 104-107: Replace with `Ensure.global_config_exists(ctx)`
|
|
169
|
+
- [ ] Lines 157-160: Replace with `Ensure.global_config_exists(ctx)`
|
|
170
|
+
- [ ] Run tests: `uv run pytest tests/commands/test_config.py`
|
|
171
|
+
- [ ] Verify no regressions
|
|
172
|
+
|
|
173
|
+
#### Task 2.9: Convert commands/init.py
|
|
174
|
+
|
|
175
|
+
- [ ] Lines 171-175: Replace with `Ensure.global_config_exists(ctx)`
|
|
176
|
+
- [ ] Lines 228-232: Replace with `Ensure.global_config_exists(ctx)`
|
|
177
|
+
- [ ] Lines 248-252: Replace with `Ensure.global_config_exists(ctx)`
|
|
178
|
+
- [ ] Lines 266-268: Replace with `Ensure.path_not_exists()` (adjust condition logic)
|
|
179
|
+
- [ ] Run tests: `uv run pytest tests/commands/test_init.py`
|
|
180
|
+
- [ ] Verify no regressions
|
|
181
|
+
|
|
182
|
+
#### Task 2.10: Convert commands/wt/current_cmd.py
|
|
183
|
+
|
|
184
|
+
- [ ] Lines 28-29: Add error message and replace with `Ensure.not_none(wt_info, "Not in a worktree")`
|
|
185
|
+
- [ ] Run tests: `uv run pytest tests/commands/workspace/test_current.py`
|
|
186
|
+
- [ ] Verify no regressions
|
|
187
|
+
|
|
188
|
+
#### Task 2.11: Convert commands/pr/checkout_cmd.py
|
|
189
|
+
|
|
190
|
+
- [ ] Line 43: Replace NoRepoSentinel check with `Ensure.in_repo(ctx)`
|
|
191
|
+
- [ ] Run tests: `uv run pytest tests/commands/pr/`
|
|
192
|
+
- [ ] Verify no regressions
|
|
193
|
+
|
|
194
|
+
#### Task 2.12: Convert cli/core.py
|
|
195
|
+
|
|
196
|
+
- [ ] Lines 18-19: Replace with `Ensure.global_config_exists(ctx)`
|
|
197
|
+
- [ ] Run tests: `uv run pytest tests/unit/cli/`
|
|
198
|
+
- [ ] Verify no regressions
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Phase 3: Final Cleanup
|
|
203
|
+
|
|
204
|
+
#### Task 3.1: Audit for remaining patterns
|
|
205
|
+
|
|
206
|
+
- [ ] Search for `raise SystemExit(1)` across CLI files
|
|
207
|
+
- [ ] Search for manual None checks followed by user_output
|
|
208
|
+
- [ ] Search for manual path existence checks
|
|
209
|
+
- [ ] Create follow-up tasks for any missed patterns
|
|
210
|
+
|
|
211
|
+
#### Task 3.2: Consistency pass
|
|
212
|
+
|
|
213
|
+
- [ ] Verify all error messages use red "Error: " prefix
|
|
214
|
+
- [ ] Verify all Ensure method docstrings are complete
|
|
215
|
+
- [ ] Verify all type narrowing methods work with ty
|
|
216
|
+
- [ ] Update any developer documentation
|
|
217
|
+
|
|
218
|
+
#### Task 3.3: Final validation
|
|
219
|
+
|
|
220
|
+
- [ ] Run full test suite: `uv run pytest`
|
|
221
|
+
- [ ] Run type checker: `uv run ty`
|
|
222
|
+
- [ ] Verify no regressions in any tests
|
|
223
|
+
- [ ] Mark project as COMPLETED
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Implementation Notes
|
|
228
|
+
|
|
229
|
+
### Test Pattern to Follow
|
|
230
|
+
|
|
231
|
+
From `tests/unit/cli/test_ensure.py`:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
def test_exits_when_condition_false(self) -> None:
|
|
235
|
+
"""Ensure.method raises SystemExit when condition fails."""
|
|
236
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
237
|
+
Ensure.method(...)
|
|
238
|
+
assert exc_info.value.code == 1
|
|
239
|
+
|
|
240
|
+
def test_error_message_output(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
241
|
+
"""Ensure.method outputs error message to stderr."""
|
|
242
|
+
with pytest.raises(SystemExit):
|
|
243
|
+
Ensure.method(...)
|
|
244
|
+
|
|
245
|
+
captured = capsys.readouterr()
|
|
246
|
+
assert "Error:" in captured.err
|
|
247
|
+
assert "expected message" in captured.err
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Type Narrowing Example
|
|
251
|
+
|
|
252
|
+
Methods that return values should narrow types:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# Before: branch: str | None = ctx.git.get_current_branch(ctx.cwd)
|
|
256
|
+
# After: branch: str = Ensure.not_detached_head(ctx, ctx.git.get_current_branch(ctx.cwd), "operation")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Mutually Exclusive Flags Example
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# Before:
|
|
263
|
+
if flag_count > 1:
|
|
264
|
+
user_output("Error: Only one of --current, --branch, or --worktree can be specified")
|
|
265
|
+
raise SystemExit(1)
|
|
266
|
+
|
|
267
|
+
# After:
|
|
268
|
+
Ensure.mutually_exclusive_flags({
|
|
269
|
+
"--current": current,
|
|
270
|
+
"--branch": branch is not None,
|
|
271
|
+
"--worktree": worktree is not None
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Progress Tracking
|
|
278
|
+
|
|
279
|
+
| Phase | Tasks | Completed | Remaining |
|
|
280
|
+
| ------------------------- | ------ | --------- | --------- |
|
|
281
|
+
| Phase 1: New Methods | 8 | 0 | 8 |
|
|
282
|
+
| Phase 2: File Conversions | 12 | 0 | 12 |
|
|
283
|
+
| Phase 3: Final Cleanup | 3 | 0 | 3 |
|
|
284
|
+
| **TOTAL** | **23** | **0** | **23** |
|
|
285
|
+
|
|
286
|
+
**Last Updated**: 2025-11-26
|
|
287
|
+
**Current Focus**: Phase 1 - New Ensure Methods
|
|
288
|
+
**Next Up**: Task 1.1 - Implement Ensure.mutually_exclusive_flags()
|