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/core/context.py
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
"""Application context with dependency injection.
|
|
2
|
+
|
|
3
|
+
This module provides factory functions for erk CLI context creation.
|
|
4
|
+
The unified ErkContext dataclass is defined in erk_shared.context and
|
|
5
|
+
re-exported here for backwards compatibility.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import shutil
|
|
9
|
+
from collections.abc import MutableMapping
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import tomlkit
|
|
15
|
+
|
|
16
|
+
from erk.cli.config import load_config
|
|
17
|
+
from erk.core.claude_executor import RealClaudeExecutor
|
|
18
|
+
from erk.core.completion import RealCompletion
|
|
19
|
+
from erk.core.implementation_queue.github.real import RealGitHubAdmin
|
|
20
|
+
from erk.core.planner.registry_real import RealPlannerRegistry
|
|
21
|
+
from erk.core.repo_discovery import discover_repo_or_sentinel, ensure_erk_metadata_dir
|
|
22
|
+
from erk.core.script_writer import RealScriptWriter
|
|
23
|
+
from erk.core.services.plan_list_service import RealPlanListService
|
|
24
|
+
from erk.core.shell import RealShell
|
|
25
|
+
|
|
26
|
+
# Re-export ErkContext from erk_shared for isinstance() compatibility
|
|
27
|
+
# This ensures that both erk CLI and kit commands use the same class identity
|
|
28
|
+
from erk_shared.context.context import ErkContext as ErkContext
|
|
29
|
+
|
|
30
|
+
# Re-export types from erk_shared.context
|
|
31
|
+
from erk_shared.context.types import GlobalConfig as GlobalConfig
|
|
32
|
+
from erk_shared.context.types import LoadedConfig as LoadedConfig
|
|
33
|
+
from erk_shared.context.types import NoRepoSentinel as NoRepoSentinel
|
|
34
|
+
from erk_shared.context.types import RepoContext as RepoContext
|
|
35
|
+
|
|
36
|
+
# Import ABCs and fakes from erk_shared.core
|
|
37
|
+
from erk_shared.core import (
|
|
38
|
+
ClaudeExecutor,
|
|
39
|
+
FakePlanListService,
|
|
40
|
+
PlanListService,
|
|
41
|
+
PlannerRegistry,
|
|
42
|
+
ScriptWriter,
|
|
43
|
+
)
|
|
44
|
+
from erk_shared.extraction.claude_installation import ClaudeInstallation
|
|
45
|
+
from erk_shared.gateway.claude_settings.abc import ClaudeSettingsStore
|
|
46
|
+
from erk_shared.gateway.claude_settings.real import RealClaudeSettingsStore
|
|
47
|
+
|
|
48
|
+
# Import erk-specific integrations
|
|
49
|
+
from erk_shared.gateway.completion import Completion
|
|
50
|
+
from erk_shared.gateway.erk_installation.abc import ErkInstallation
|
|
51
|
+
from erk_shared.gateway.erk_installation.real import RealErkInstallation
|
|
52
|
+
from erk_shared.gateway.feedback import InteractiveFeedback, SuppressedFeedback, UserFeedback
|
|
53
|
+
from erk_shared.gateway.graphite.abc import Graphite
|
|
54
|
+
from erk_shared.gateway.graphite.disabled import (
|
|
55
|
+
GraphiteDisabled,
|
|
56
|
+
GraphiteDisabledReason,
|
|
57
|
+
)
|
|
58
|
+
from erk_shared.gateway.graphite.dry_run import DryRunGraphite
|
|
59
|
+
from erk_shared.gateway.graphite.real import RealGraphite
|
|
60
|
+
from erk_shared.gateway.shell import Shell
|
|
61
|
+
from erk_shared.gateway.time.abc import Time
|
|
62
|
+
from erk_shared.gateway.time.real import RealTime
|
|
63
|
+
from erk_shared.git.abc import Git
|
|
64
|
+
from erk_shared.git.dry_run import DryRunGit
|
|
65
|
+
from erk_shared.git.real import RealGit
|
|
66
|
+
from erk_shared.github.abc import GitHub
|
|
67
|
+
from erk_shared.github.dry_run import DryRunGitHub
|
|
68
|
+
from erk_shared.github.issues import DryRunGitHubIssues, GitHubIssues, RealGitHubIssues
|
|
69
|
+
from erk_shared.github.parsing import parse_git_remote_url
|
|
70
|
+
from erk_shared.github.real import RealGitHub
|
|
71
|
+
from erk_shared.github.types import RepoInfo
|
|
72
|
+
from erk_shared.github_admin.abc import GitHubAdmin
|
|
73
|
+
from erk_shared.output.output import user_output
|
|
74
|
+
from erk_shared.plan_store.github import GitHubPlanStore
|
|
75
|
+
from erk_shared.plan_store.store import PlanStore
|
|
76
|
+
from erk_shared.prompt_executor import PromptExecutor
|
|
77
|
+
from erk_shared.prompt_executor.real import RealPromptExecutor
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def minimal_context(git: Git, cwd: Path, dry_run: bool = False) -> ErkContext:
|
|
81
|
+
"""Create minimal context with only git configured, rest are test defaults.
|
|
82
|
+
|
|
83
|
+
Useful for simple tests that only need git operations. Other integration
|
|
84
|
+
classes are initialized with their standard test defaults (fake implementations).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
git: The Git implementation (usually FakeGit with test configuration)
|
|
88
|
+
cwd: Current working directory path for the context
|
|
89
|
+
dry_run: Whether to enable dry-run mode (default False)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
ErkContext with git configured and other dependencies using test defaults
|
|
93
|
+
|
|
94
|
+
Note:
|
|
95
|
+
For more complex test setup with custom configs or multiple integration classes,
|
|
96
|
+
use context_for_test() instead.
|
|
97
|
+
"""
|
|
98
|
+
from tests.fakes.claude_executor import FakeClaudeExecutor
|
|
99
|
+
from tests.fakes.script_writer import FakeScriptWriter
|
|
100
|
+
|
|
101
|
+
from erk.core.planner.registry_fake import FakePlannerRegistry
|
|
102
|
+
from erk_shared.extraction.claude_installation import FakeClaudeInstallation
|
|
103
|
+
from erk_shared.gateway.claude_settings.fake import FakeClaudeSettingsStore
|
|
104
|
+
from erk_shared.gateway.completion import FakeCompletion
|
|
105
|
+
from erk_shared.gateway.erk_installation.fake import FakeErkInstallation
|
|
106
|
+
from erk_shared.gateway.feedback import FakeUserFeedback
|
|
107
|
+
from erk_shared.gateway.graphite.fake import FakeGraphite
|
|
108
|
+
from erk_shared.gateway.shell import FakeShell
|
|
109
|
+
from erk_shared.gateway.time.fake import FakeTime
|
|
110
|
+
from erk_shared.github.fake import FakeGitHub
|
|
111
|
+
from erk_shared.github.issues import FakeGitHubIssues
|
|
112
|
+
from erk_shared.github_admin.fake import FakeGitHubAdmin
|
|
113
|
+
from erk_shared.prompt_executor.fake import FakePromptExecutor
|
|
114
|
+
|
|
115
|
+
fake_github = FakeGitHub()
|
|
116
|
+
fake_issues = FakeGitHubIssues()
|
|
117
|
+
fake_graphite = FakeGraphite()
|
|
118
|
+
fake_time = FakeTime()
|
|
119
|
+
return ErkContext(
|
|
120
|
+
git=git,
|
|
121
|
+
github=fake_github,
|
|
122
|
+
github_admin=FakeGitHubAdmin(),
|
|
123
|
+
issues=fake_issues,
|
|
124
|
+
plan_store=GitHubPlanStore(fake_issues, fake_time),
|
|
125
|
+
graphite=fake_graphite,
|
|
126
|
+
shell=FakeShell(),
|
|
127
|
+
claude_executor=FakeClaudeExecutor(),
|
|
128
|
+
completion=FakeCompletion(),
|
|
129
|
+
time=fake_time,
|
|
130
|
+
erk_installation=FakeErkInstallation(),
|
|
131
|
+
script_writer=FakeScriptWriter(),
|
|
132
|
+
feedback=FakeUserFeedback(),
|
|
133
|
+
plan_list_service=FakePlanListService(),
|
|
134
|
+
planner_registry=FakePlannerRegistry(),
|
|
135
|
+
claude_installation=FakeClaudeInstallation.for_test(),
|
|
136
|
+
prompt_executor=FakePromptExecutor(),
|
|
137
|
+
claude_settings_store=FakeClaudeSettingsStore(),
|
|
138
|
+
cwd=cwd,
|
|
139
|
+
global_config=None,
|
|
140
|
+
local_config=LoadedConfig.test(),
|
|
141
|
+
repo=NoRepoSentinel(),
|
|
142
|
+
repo_info=None,
|
|
143
|
+
dry_run=dry_run,
|
|
144
|
+
debug=False,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def context_for_test(
|
|
149
|
+
git: Git | None = None,
|
|
150
|
+
github: GitHub | None = None,
|
|
151
|
+
github_admin: GitHubAdmin | None = None,
|
|
152
|
+
issues: GitHubIssues | None = None,
|
|
153
|
+
plan_store: PlanStore | None = None,
|
|
154
|
+
graphite: Graphite | None = None,
|
|
155
|
+
shell: Shell | None = None,
|
|
156
|
+
claude_executor: ClaudeExecutor | None = None,
|
|
157
|
+
completion: Completion | None = None,
|
|
158
|
+
time: Time | None = None,
|
|
159
|
+
erk_installation: ErkInstallation | None = None,
|
|
160
|
+
script_writer: ScriptWriter | None = None,
|
|
161
|
+
feedback: UserFeedback | None = None,
|
|
162
|
+
plan_list_service: PlanListService | None = None,
|
|
163
|
+
planner_registry: PlannerRegistry | None = None,
|
|
164
|
+
claude_installation: ClaudeInstallation | None = None,
|
|
165
|
+
prompt_executor: PromptExecutor | None = None,
|
|
166
|
+
cwd: Path | None = None,
|
|
167
|
+
global_config: GlobalConfig | None = None,
|
|
168
|
+
local_config: LoadedConfig | None = None,
|
|
169
|
+
repo: RepoContext | NoRepoSentinel | None = None,
|
|
170
|
+
repo_info: RepoInfo | None = None,
|
|
171
|
+
dry_run: bool = False,
|
|
172
|
+
debug: bool = False,
|
|
173
|
+
) -> ErkContext:
|
|
174
|
+
"""Create test context with optional pre-configured integration classes.
|
|
175
|
+
|
|
176
|
+
Provides full control over all context parameters with sensible test defaults
|
|
177
|
+
for any unspecified values. Use this for complex test scenarios that need
|
|
178
|
+
specific configurations for multiple integration classes.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
git: Optional Git implementation. If None, creates empty FakeGit.
|
|
182
|
+
github: Optional GitHub implementation. If None, creates empty FakeGitHub.
|
|
183
|
+
issues: Optional GitHubIssues implementation.
|
|
184
|
+
If None, creates empty FakeGitHubIssues.
|
|
185
|
+
graphite: Optional Graphite implementation.
|
|
186
|
+
If None, creates empty FakeGraphite.
|
|
187
|
+
shell: Optional Shell implementation. If None, creates empty FakeShell.
|
|
188
|
+
completion: Optional Completion implementation.
|
|
189
|
+
If None, creates empty FakeCompletion.
|
|
190
|
+
erk_installation: Optional ErkInstallation implementation.
|
|
191
|
+
If None, creates FakeErkInstallation with test config.
|
|
192
|
+
script_writer: Optional ScriptWriter implementation.
|
|
193
|
+
If None, creates empty FakeScriptWriter.
|
|
194
|
+
feedback: Optional UserFeedback implementation.
|
|
195
|
+
If None, creates FakeUserFeedback.
|
|
196
|
+
prompt_executor: Optional PromptExecutor. If None, creates FakePromptExecutor.
|
|
197
|
+
cwd: Optional current working directory. If None, uses sentinel_path().
|
|
198
|
+
global_config: Optional GlobalConfig. If None, uses test defaults.
|
|
199
|
+
local_config: Optional LoadedConfig. If None, uses empty defaults.
|
|
200
|
+
repo: Optional RepoContext or NoRepoSentinel. If None, uses NoRepoSentinel().
|
|
201
|
+
repo_info: Optional RepoInfo. If None, stays None.
|
|
202
|
+
dry_run: Whether to enable dry-run mode (default False).
|
|
203
|
+
debug: Whether to enable debug mode (default False).
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
ErkContext configured with provided values and test defaults
|
|
207
|
+
"""
|
|
208
|
+
from tests.fakes.claude_executor import FakeClaudeExecutor
|
|
209
|
+
from tests.fakes.script_writer import FakeScriptWriter
|
|
210
|
+
from tests.test_utils.paths import sentinel_path
|
|
211
|
+
|
|
212
|
+
from erk.core.planner.registry_fake import FakePlannerRegistry
|
|
213
|
+
from erk_shared.extraction.claude_installation import FakeClaudeInstallation
|
|
214
|
+
from erk_shared.gateway.claude_settings.fake import FakeClaudeSettingsStore
|
|
215
|
+
from erk_shared.gateway.completion import FakeCompletion
|
|
216
|
+
from erk_shared.gateway.erk_installation.fake import FakeErkInstallation
|
|
217
|
+
from erk_shared.gateway.feedback import FakeUserFeedback
|
|
218
|
+
from erk_shared.gateway.graphite.dry_run import DryRunGraphite
|
|
219
|
+
from erk_shared.gateway.graphite.fake import FakeGraphite
|
|
220
|
+
from erk_shared.gateway.shell import FakeShell
|
|
221
|
+
from erk_shared.gateway.time.fake import FakeTime
|
|
222
|
+
from erk_shared.git.fake import FakeGit
|
|
223
|
+
from erk_shared.github.fake import FakeGitHub
|
|
224
|
+
from erk_shared.github.issues import FakeGitHubIssues
|
|
225
|
+
from erk_shared.github_admin.fake import FakeGitHubAdmin
|
|
226
|
+
from erk_shared.prompt_executor.fake import FakePromptExecutor
|
|
227
|
+
|
|
228
|
+
if git is None:
|
|
229
|
+
git = FakeGit()
|
|
230
|
+
|
|
231
|
+
if github is None:
|
|
232
|
+
github = FakeGitHub()
|
|
233
|
+
|
|
234
|
+
if github_admin is None:
|
|
235
|
+
github_admin = FakeGitHubAdmin()
|
|
236
|
+
|
|
237
|
+
if issues is None:
|
|
238
|
+
issues = FakeGitHubIssues()
|
|
239
|
+
|
|
240
|
+
if plan_store is None:
|
|
241
|
+
# Always compose from issues layer - no separate FakePlanStore
|
|
242
|
+
# This ensures tests use the same composition as production code
|
|
243
|
+
plan_store = GitHubPlanStore(issues)
|
|
244
|
+
|
|
245
|
+
if graphite is None:
|
|
246
|
+
graphite = FakeGraphite()
|
|
247
|
+
|
|
248
|
+
if shell is None:
|
|
249
|
+
shell = FakeShell()
|
|
250
|
+
|
|
251
|
+
if claude_executor is None:
|
|
252
|
+
claude_executor = FakeClaudeExecutor()
|
|
253
|
+
|
|
254
|
+
if completion is None:
|
|
255
|
+
completion = FakeCompletion()
|
|
256
|
+
|
|
257
|
+
if time is None:
|
|
258
|
+
time = FakeTime()
|
|
259
|
+
|
|
260
|
+
if script_writer is None:
|
|
261
|
+
script_writer = FakeScriptWriter()
|
|
262
|
+
|
|
263
|
+
if feedback is None:
|
|
264
|
+
feedback = FakeUserFeedback()
|
|
265
|
+
|
|
266
|
+
if plan_list_service is None:
|
|
267
|
+
# If github and issues were provided, wire them up via RealPlanListService
|
|
268
|
+
# so that tests get realistic behavior when testing plan list functionality
|
|
269
|
+
plan_list_service = RealPlanListService(github, issues)
|
|
270
|
+
|
|
271
|
+
if planner_registry is None:
|
|
272
|
+
planner_registry = FakePlannerRegistry()
|
|
273
|
+
|
|
274
|
+
if claude_installation is None:
|
|
275
|
+
claude_installation = FakeClaudeInstallation.for_test()
|
|
276
|
+
|
|
277
|
+
if prompt_executor is None:
|
|
278
|
+
prompt_executor = FakePromptExecutor()
|
|
279
|
+
|
|
280
|
+
if global_config is None:
|
|
281
|
+
global_config = GlobalConfig(
|
|
282
|
+
erk_root=Path("/test/erks"),
|
|
283
|
+
use_graphite=False,
|
|
284
|
+
shell_setup_complete=False,
|
|
285
|
+
show_pr_info=True,
|
|
286
|
+
github_planning=True,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if erk_installation is None:
|
|
290
|
+
erk_installation = FakeErkInstallation(config=global_config)
|
|
291
|
+
|
|
292
|
+
if local_config is None:
|
|
293
|
+
local_config = LoadedConfig.test()
|
|
294
|
+
|
|
295
|
+
if repo is None:
|
|
296
|
+
repo = NoRepoSentinel()
|
|
297
|
+
|
|
298
|
+
# Apply dry-run wrappers if needed (matching production behavior)
|
|
299
|
+
if dry_run:
|
|
300
|
+
git = DryRunGit(git)
|
|
301
|
+
graphite = DryRunGraphite(graphite)
|
|
302
|
+
github = DryRunGitHub(github)
|
|
303
|
+
issues = DryRunGitHubIssues(issues)
|
|
304
|
+
|
|
305
|
+
return ErkContext(
|
|
306
|
+
git=git,
|
|
307
|
+
github=github,
|
|
308
|
+
github_admin=github_admin,
|
|
309
|
+
issues=issues,
|
|
310
|
+
plan_store=plan_store,
|
|
311
|
+
graphite=graphite,
|
|
312
|
+
shell=shell,
|
|
313
|
+
claude_executor=claude_executor,
|
|
314
|
+
completion=completion,
|
|
315
|
+
time=time,
|
|
316
|
+
erk_installation=erk_installation,
|
|
317
|
+
script_writer=script_writer,
|
|
318
|
+
feedback=feedback,
|
|
319
|
+
plan_list_service=plan_list_service,
|
|
320
|
+
planner_registry=planner_registry,
|
|
321
|
+
claude_installation=claude_installation,
|
|
322
|
+
prompt_executor=prompt_executor,
|
|
323
|
+
claude_settings_store=FakeClaudeSettingsStore(),
|
|
324
|
+
cwd=cwd or sentinel_path(),
|
|
325
|
+
global_config=global_config,
|
|
326
|
+
local_config=local_config,
|
|
327
|
+
repo=repo,
|
|
328
|
+
repo_info=repo_info,
|
|
329
|
+
dry_run=dry_run,
|
|
330
|
+
debug=debug,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def write_trunk_to_pyproject(repo_root: Path, trunk: str, git: Git | None = None) -> None:
|
|
335
|
+
"""Write trunk branch configuration to pyproject.toml.
|
|
336
|
+
|
|
337
|
+
Creates or updates the [tool.erk] section with trunk_branch setting.
|
|
338
|
+
Preserves existing formatting and comments using tomlkit.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
repo_root: Path to the repository root directory
|
|
342
|
+
trunk: Trunk branch name to configure
|
|
343
|
+
git: Optional Git interface for path checking (uses .exists() if None)
|
|
344
|
+
"""
|
|
345
|
+
pyproject_path = repo_root / "pyproject.toml"
|
|
346
|
+
|
|
347
|
+
# Check existence using git if available (for test compatibility)
|
|
348
|
+
if git is not None:
|
|
349
|
+
path_exists = git.path_exists(pyproject_path)
|
|
350
|
+
else:
|
|
351
|
+
path_exists = pyproject_path.exists()
|
|
352
|
+
|
|
353
|
+
# Load existing file or create new document
|
|
354
|
+
if path_exists:
|
|
355
|
+
with pyproject_path.open("r", encoding="utf-8") as f:
|
|
356
|
+
doc = tomlkit.load(f)
|
|
357
|
+
else:
|
|
358
|
+
doc = tomlkit.document()
|
|
359
|
+
|
|
360
|
+
# Ensure [tool] section exists
|
|
361
|
+
if "tool" not in doc:
|
|
362
|
+
assert isinstance(doc, MutableMapping), f"Expected MutableMapping, got {type(doc)}"
|
|
363
|
+
cast(dict[str, Any], doc)["tool"] = tomlkit.table()
|
|
364
|
+
|
|
365
|
+
# Ensure [tool.erk] section exists
|
|
366
|
+
tool_section = cast(dict[str, Any], doc["tool"])
|
|
367
|
+
if "erk" not in tool_section:
|
|
368
|
+
tool_section["erk"] = tomlkit.table()
|
|
369
|
+
|
|
370
|
+
# Set trunk_branch value
|
|
371
|
+
cast(dict[str, Any], tool_section["erk"])["trunk_branch"] = trunk
|
|
372
|
+
|
|
373
|
+
# Write back to file
|
|
374
|
+
with pyproject_path.open("w", encoding="utf-8") as f:
|
|
375
|
+
tomlkit.dump(doc, f)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def safe_cwd() -> tuple[Path | None, str | None]:
|
|
379
|
+
"""Get current working directory, detecting if it no longer exists.
|
|
380
|
+
|
|
381
|
+
Uses LBYL approach: checks if the operation will succeed before attempting it.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
tuple[Path | None, str | None]: (path, error_message)
|
|
385
|
+
- If successful: (Path, None)
|
|
386
|
+
- If directory deleted: (None, error_message)
|
|
387
|
+
|
|
388
|
+
Note:
|
|
389
|
+
This is an acceptable use of try/except since we're wrapping a third-party
|
|
390
|
+
API (Path.cwd()) that provides no way to check the condition first.
|
|
391
|
+
"""
|
|
392
|
+
try:
|
|
393
|
+
cwd_path = Path.cwd()
|
|
394
|
+
return (cwd_path, None)
|
|
395
|
+
except (FileNotFoundError, OSError):
|
|
396
|
+
return (
|
|
397
|
+
None,
|
|
398
|
+
"Current working directory no longer exists",
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def create_context(*, dry_run: bool, script: bool = False, debug: bool = False) -> ErkContext:
|
|
403
|
+
"""Create production context with real implementations.
|
|
404
|
+
|
|
405
|
+
Called at CLI entry point to create the context for the entire
|
|
406
|
+
command execution.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
dry_run: If True, wrap all dependencies with dry-run wrappers that
|
|
410
|
+
print intended actions without executing them
|
|
411
|
+
script: If True, use SuppressedFeedback to suppress diagnostic output
|
|
412
|
+
for shell integration mode (default False)
|
|
413
|
+
debug: If True, enable debug mode for error handling (default False)
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
ErkContext with real implementations, wrapped in dry-run
|
|
417
|
+
wrappers if dry_run=True
|
|
418
|
+
|
|
419
|
+
Example:
|
|
420
|
+
>>> ctx = create_context(dry_run=False, script=False)
|
|
421
|
+
>>> worktrees = ctx.git.list_worktrees(Path("/repo"))
|
|
422
|
+
>>> erk_root = ctx.global_config.erk_root
|
|
423
|
+
"""
|
|
424
|
+
# 1. Capture cwd (no deps)
|
|
425
|
+
cwd_result, error_msg = safe_cwd()
|
|
426
|
+
if cwd_result is None:
|
|
427
|
+
assert error_msg is not None
|
|
428
|
+
# Emit clear error and exit
|
|
429
|
+
user_output(click.style("Error: ", fg="red") + error_msg)
|
|
430
|
+
user_output("\nThe directory you're running from has been deleted.")
|
|
431
|
+
user_output("Please change to a valid directory and try again.")
|
|
432
|
+
raise SystemExit(1)
|
|
433
|
+
|
|
434
|
+
cwd = cwd_result
|
|
435
|
+
|
|
436
|
+
# 2. Create erk installation gateway
|
|
437
|
+
erk_installation = RealErkInstallation()
|
|
438
|
+
|
|
439
|
+
# 3. Load global config (no deps) - None if not exists (for init command)
|
|
440
|
+
global_config: GlobalConfig | None
|
|
441
|
+
if erk_installation.config_exists():
|
|
442
|
+
global_config = erk_installation.load_config()
|
|
443
|
+
else:
|
|
444
|
+
# For init command only: config doesn't exist yet
|
|
445
|
+
global_config = None
|
|
446
|
+
|
|
447
|
+
# 4. Create integration classes (need git for repo discovery)
|
|
448
|
+
# Create time first so it can be injected into other classes
|
|
449
|
+
time: Time = RealTime()
|
|
450
|
+
git: Git = RealGit()
|
|
451
|
+
|
|
452
|
+
# Create Graphite based on config and availability
|
|
453
|
+
graphite: Graphite
|
|
454
|
+
if global_config is not None and global_config.use_graphite:
|
|
455
|
+
# Config says use Graphite - check if gt is installed
|
|
456
|
+
if shutil.which("gt") is None:
|
|
457
|
+
graphite = GraphiteDisabled(GraphiteDisabledReason.NOT_INSTALLED)
|
|
458
|
+
else:
|
|
459
|
+
graphite = RealGraphite()
|
|
460
|
+
else:
|
|
461
|
+
# Graphite disabled by config (or config doesn't exist yet)
|
|
462
|
+
graphite = GraphiteDisabled(GraphiteDisabledReason.CONFIG_DISABLED)
|
|
463
|
+
|
|
464
|
+
# 5. Discover repo (only needs cwd, erk_root, git)
|
|
465
|
+
# If global_config is None, use placeholder path for repo discovery
|
|
466
|
+
erk_root = global_config.erk_root if global_config else erk_installation.root() / "worktrees"
|
|
467
|
+
repo = discover_repo_or_sentinel(cwd, erk_root, git)
|
|
468
|
+
|
|
469
|
+
# 6. Fetch repo_info (if in a repo with origin remote)
|
|
470
|
+
# Note: try-except is acceptable at CLI entry point boundary per LBYL conventions
|
|
471
|
+
repo_info: RepoInfo | None = None
|
|
472
|
+
if not isinstance(repo, NoRepoSentinel):
|
|
473
|
+
try:
|
|
474
|
+
remote_url = git.get_remote_url(repo.root)
|
|
475
|
+
owner, name = parse_git_remote_url(remote_url)
|
|
476
|
+
repo_info = RepoInfo(owner=owner, name=name)
|
|
477
|
+
except ValueError:
|
|
478
|
+
# No origin remote configured - repo_info stays None
|
|
479
|
+
pass
|
|
480
|
+
|
|
481
|
+
# 7. Load local config (or defaults if no repo)
|
|
482
|
+
# Loaded early so plans_repo can be used for GitHubIssues
|
|
483
|
+
if isinstance(repo, NoRepoSentinel):
|
|
484
|
+
local_config = LoadedConfig.test()
|
|
485
|
+
else:
|
|
486
|
+
# Ensure metadata directories exist (needed for worktrees)
|
|
487
|
+
ensure_erk_metadata_dir(repo)
|
|
488
|
+
# Load config from primary location (.erk/config.toml)
|
|
489
|
+
# Legacy locations are detected by 'erk doctor' only
|
|
490
|
+
local_config = load_config(repo.root)
|
|
491
|
+
|
|
492
|
+
# 8. Create GitHub-related classes (need repo_info, local_config)
|
|
493
|
+
github: GitHub = RealGitHub(time, repo_info)
|
|
494
|
+
# Use plans_repo for cross-repo plan issue management if configured
|
|
495
|
+
issues: GitHubIssues = RealGitHubIssues(target_repo=local_config.plans_repo)
|
|
496
|
+
plan_store: PlanStore = GitHubPlanStore(issues)
|
|
497
|
+
plan_list_service: PlanListService = RealPlanListService(github, issues)
|
|
498
|
+
|
|
499
|
+
# 9. Choose feedback implementation based on mode
|
|
500
|
+
feedback: UserFeedback
|
|
501
|
+
if script:
|
|
502
|
+
feedback = SuppressedFeedback() # Suppress diagnostics
|
|
503
|
+
else:
|
|
504
|
+
feedback = InteractiveFeedback() # Show all messages
|
|
505
|
+
|
|
506
|
+
# 10. Apply dry-run wrappers if needed
|
|
507
|
+
if dry_run:
|
|
508
|
+
git = DryRunGit(git)
|
|
509
|
+
graphite = DryRunGraphite(graphite)
|
|
510
|
+
github = DryRunGitHub(github)
|
|
511
|
+
issues = DryRunGitHubIssues(issues)
|
|
512
|
+
|
|
513
|
+
# 11. Create claude installation, prompt executor, and claude settings store
|
|
514
|
+
from erk_shared.extraction.claude_installation import RealClaudeInstallation
|
|
515
|
+
|
|
516
|
+
real_claude_installation: ClaudeInstallation = RealClaudeInstallation()
|
|
517
|
+
prompt_executor: PromptExecutor = RealPromptExecutor(time)
|
|
518
|
+
claude_settings_store: ClaudeSettingsStore = RealClaudeSettingsStore()
|
|
519
|
+
|
|
520
|
+
# 13. Create context with all values
|
|
521
|
+
return ErkContext(
|
|
522
|
+
git=git,
|
|
523
|
+
github=github,
|
|
524
|
+
github_admin=RealGitHubAdmin(),
|
|
525
|
+
issues=issues,
|
|
526
|
+
plan_store=plan_store,
|
|
527
|
+
graphite=graphite,
|
|
528
|
+
shell=RealShell(),
|
|
529
|
+
claude_executor=RealClaudeExecutor(),
|
|
530
|
+
completion=RealCompletion(),
|
|
531
|
+
time=time,
|
|
532
|
+
erk_installation=erk_installation,
|
|
533
|
+
script_writer=RealScriptWriter(),
|
|
534
|
+
feedback=feedback,
|
|
535
|
+
plan_list_service=plan_list_service,
|
|
536
|
+
planner_registry=RealPlannerRegistry(erk_installation.get_planners_config_path()),
|
|
537
|
+
claude_installation=real_claude_installation,
|
|
538
|
+
prompt_executor=prompt_executor,
|
|
539
|
+
claude_settings_store=claude_settings_store,
|
|
540
|
+
cwd=cwd,
|
|
541
|
+
global_config=global_config,
|
|
542
|
+
local_config=local_config,
|
|
543
|
+
repo=repo,
|
|
544
|
+
repo_info=repo_info,
|
|
545
|
+
dry_run=dry_run,
|
|
546
|
+
debug=debug,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def regenerate_context(existing_ctx: ErkContext) -> ErkContext:
|
|
551
|
+
"""Regenerate context with fresh cwd.
|
|
552
|
+
|
|
553
|
+
Creates a new ErkContext with:
|
|
554
|
+
- Current working directory (Path.cwd())
|
|
555
|
+
- Preserved dry_run state and operation instances
|
|
556
|
+
|
|
557
|
+
Use this after mutations like os.chdir() or worktree removal
|
|
558
|
+
to ensure ctx.cwd reflects actual current directory.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
existing_ctx: Current context to preserve settings from
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
New ErkContext with regenerated state
|
|
565
|
+
|
|
566
|
+
Example:
|
|
567
|
+
# After os.chdir() or worktree removal
|
|
568
|
+
ctx = regenerate_context(ctx)
|
|
569
|
+
"""
|
|
570
|
+
return create_context(dry_run=existing_ctx.dry_run, debug=existing_ctx.debug)
|
erk/core/display/abc.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Abstract interface for live-updating terminal display."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from rich.console import RenderableType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LiveDisplay(ABC):
|
|
9
|
+
"""Abstract interface for live-updating terminal display."""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def start(self) -> None:
|
|
13
|
+
"""Start live display mode."""
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def update(self, renderable: RenderableType) -> None:
|
|
18
|
+
"""Update the display with new content."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def stop(self) -> None:
|
|
23
|
+
"""Stop live display mode."""
|
|
24
|
+
...
|
erk/core/display/real.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Real implementation of LiveDisplay using Rich Live."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console, RenderableType
|
|
4
|
+
from rich.live import Live
|
|
5
|
+
|
|
6
|
+
from erk.core.display.abc import LiveDisplay
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RealLiveDisplay(LiveDisplay):
|
|
10
|
+
"""Real implementation using Rich Live."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, console: Console | None = None) -> None:
|
|
13
|
+
self._console = console or Console(stderr=True, width=200, force_terminal=True)
|
|
14
|
+
self._live: Live | None = None
|
|
15
|
+
|
|
16
|
+
def start(self) -> None:
|
|
17
|
+
"""Start live display mode."""
|
|
18
|
+
self._live = Live(console=self._console, refresh_per_second=4)
|
|
19
|
+
self._live.start()
|
|
20
|
+
|
|
21
|
+
def update(self, renderable: RenderableType) -> None:
|
|
22
|
+
"""Update the display with new content."""
|
|
23
|
+
if self._live is not None:
|
|
24
|
+
self._live.update(renderable)
|
|
25
|
+
|
|
26
|
+
def stop(self) -> None:
|
|
27
|
+
"""Stop live display mode."""
|
|
28
|
+
if self._live is not None:
|
|
29
|
+
self._live.stop()
|
|
30
|
+
self._live = None
|