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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Slot init-pool command - proactively initialize pool slots."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.slot.common import (
|
|
6
|
+
generate_slot_name,
|
|
7
|
+
get_placeholder_branch_name,
|
|
8
|
+
get_pool_size,
|
|
9
|
+
is_slot_initialized,
|
|
10
|
+
)
|
|
11
|
+
from erk.cli.core import discover_repo_context
|
|
12
|
+
from erk.core.context import ErkContext, create_context
|
|
13
|
+
from erk.core.repo_discovery import ensure_erk_metadata_dir
|
|
14
|
+
from erk.core.worktree_pool import (
|
|
15
|
+
PoolState,
|
|
16
|
+
SlotInfo,
|
|
17
|
+
load_pool_state,
|
|
18
|
+
save_pool_state,
|
|
19
|
+
)
|
|
20
|
+
from erk_shared.output.output import user_output
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command("init-pool")
|
|
24
|
+
@click.option(
|
|
25
|
+
"-n",
|
|
26
|
+
"--count",
|
|
27
|
+
type=int,
|
|
28
|
+
help="Number of slots to initialize. Defaults to pool_size from config.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--dry-run",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="Print what would be done without executing destructive operations.",
|
|
34
|
+
)
|
|
35
|
+
@click.pass_obj
|
|
36
|
+
def slot_init_pool(ctx: ErkContext, count: int | None, *, dry_run: bool) -> None:
|
|
37
|
+
"""Initialize pool slots with worktrees and placeholder branches.
|
|
38
|
+
|
|
39
|
+
Pre-creates worktrees with placeholder branches so they're ready for
|
|
40
|
+
immediate assignment. This makes `erk slot create` faster because it can
|
|
41
|
+
reuse existing worktrees instead of creating new ones.
|
|
42
|
+
|
|
43
|
+
By default, initializes slots up to the configured pool_size. Use -n to
|
|
44
|
+
specify a different count.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
erk slot init-pool # Initialize all slots up to pool_size
|
|
48
|
+
erk slot init-pool -n 2 # Initialize just 2 slots
|
|
49
|
+
erk slot init-pool --dry-run # Preview without executing
|
|
50
|
+
"""
|
|
51
|
+
if dry_run:
|
|
52
|
+
ctx = create_context(dry_run=True)
|
|
53
|
+
|
|
54
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
55
|
+
ensure_erk_metadata_dir(repo)
|
|
56
|
+
|
|
57
|
+
# Get effective slot count
|
|
58
|
+
pool_size = get_pool_size(ctx)
|
|
59
|
+
slot_count = count if count is not None else pool_size
|
|
60
|
+
|
|
61
|
+
# Validate slot count
|
|
62
|
+
if slot_count < 1:
|
|
63
|
+
user_output("Error: Slot count must be at least 1")
|
|
64
|
+
raise SystemExit(1) from None
|
|
65
|
+
|
|
66
|
+
if slot_count > pool_size:
|
|
67
|
+
user_output(
|
|
68
|
+
f"Warning: Requested {slot_count} slots but pool_size is {pool_size}. "
|
|
69
|
+
f"Initializing {pool_size} slots."
|
|
70
|
+
)
|
|
71
|
+
slot_count = pool_size
|
|
72
|
+
|
|
73
|
+
# Load or create pool state
|
|
74
|
+
state = load_pool_state(repo.pool_json_path)
|
|
75
|
+
if state is None:
|
|
76
|
+
state = PoolState(
|
|
77
|
+
version="1.0",
|
|
78
|
+
pool_size=pool_size,
|
|
79
|
+
slots=(),
|
|
80
|
+
assignments=(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Get trunk branch for placeholder branches
|
|
84
|
+
trunk = ctx.git.detect_trunk_branch(repo.root)
|
|
85
|
+
local_branches = ctx.git.list_local_branches(repo.root)
|
|
86
|
+
|
|
87
|
+
initialized_count = 0
|
|
88
|
+
already_initialized_count = 0
|
|
89
|
+
new_slots: list[SlotInfo] = list(state.slots)
|
|
90
|
+
|
|
91
|
+
for slot_num in range(1, slot_count + 1):
|
|
92
|
+
slot_name = generate_slot_name(slot_num)
|
|
93
|
+
worktree_path = repo.worktrees_dir / slot_name
|
|
94
|
+
|
|
95
|
+
# Check if already initialized
|
|
96
|
+
if is_slot_initialized(state, slot_name):
|
|
97
|
+
already_initialized_count += 1
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
# Get or create placeholder branch
|
|
101
|
+
placeholder_branch = get_placeholder_branch_name(slot_name)
|
|
102
|
+
if placeholder_branch is None:
|
|
103
|
+
user_output(f"Error: Could not generate placeholder branch for {slot_name}")
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if placeholder_branch not in local_branches:
|
|
107
|
+
ctx.git.create_branch(repo.root, placeholder_branch, trunk)
|
|
108
|
+
|
|
109
|
+
# Create worktree directory
|
|
110
|
+
if ctx.dry_run:
|
|
111
|
+
user_output(f"[DRY RUN] Would create directory: {worktree_path}")
|
|
112
|
+
else:
|
|
113
|
+
worktree_path.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
|
|
115
|
+
# Create worktree with placeholder branch
|
|
116
|
+
if not ctx.git.path_exists(worktree_path / ".git"):
|
|
117
|
+
ctx.git.add_worktree(
|
|
118
|
+
repo.root,
|
|
119
|
+
worktree_path,
|
|
120
|
+
branch=placeholder_branch,
|
|
121
|
+
ref=None,
|
|
122
|
+
create_branch=False,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Add to slots list
|
|
126
|
+
new_slots.append(SlotInfo(name=slot_name, last_objective_issue=None))
|
|
127
|
+
initialized_count += 1
|
|
128
|
+
if ctx.dry_run:
|
|
129
|
+
user_output(f"[DRY RUN] Would initialize {slot_name}")
|
|
130
|
+
else:
|
|
131
|
+
user_output(f" Initialized {slot_name}")
|
|
132
|
+
|
|
133
|
+
# Update and save state
|
|
134
|
+
new_state = PoolState(
|
|
135
|
+
version=state.version,
|
|
136
|
+
pool_size=state.pool_size,
|
|
137
|
+
slots=tuple(new_slots),
|
|
138
|
+
assignments=state.assignments,
|
|
139
|
+
)
|
|
140
|
+
if ctx.dry_run:
|
|
141
|
+
user_output("[DRY RUN] Would save pool state")
|
|
142
|
+
else:
|
|
143
|
+
save_pool_state(repo.pool_json_path, new_state)
|
|
144
|
+
|
|
145
|
+
# Report results
|
|
146
|
+
if initialized_count > 0:
|
|
147
|
+
if ctx.dry_run:
|
|
148
|
+
msg = f"[DRY RUN] Would initialize {initialized_count} slots"
|
|
149
|
+
else:
|
|
150
|
+
msg = click.style(f"✓ Initialized {initialized_count} slots", fg="green")
|
|
151
|
+
if already_initialized_count > 0:
|
|
152
|
+
msg += f" ({already_initialized_count} already existed)"
|
|
153
|
+
user_output(msg)
|
|
154
|
+
elif already_initialized_count > 0:
|
|
155
|
+
user_output(f"All {already_initialized_count} slots already initialized")
|
|
156
|
+
else:
|
|
157
|
+
user_output("No slots to initialize")
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Slot list command - display unified worktree pool status."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from erk.cli.alias import alias
|
|
10
|
+
from erk.cli.commands.slot.common import (
|
|
11
|
+
DEFAULT_POOL_SIZE,
|
|
12
|
+
generate_slot_name,
|
|
13
|
+
)
|
|
14
|
+
from erk.cli.core import discover_repo_context
|
|
15
|
+
from erk.core.context import ErkContext
|
|
16
|
+
from erk.core.display_utils import format_relative_time
|
|
17
|
+
from erk.core.worktree_pool import PoolState, load_pool_state
|
|
18
|
+
|
|
19
|
+
SlotStatus = Literal["available", "assigned", "error"]
|
|
20
|
+
SlotReason = Literal["worktree-missing", "branch-mismatch", "-"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _determine_slot_status(
|
|
24
|
+
slot_name: str,
|
|
25
|
+
assigned_slots: set[str],
|
|
26
|
+
reason: SlotReason,
|
|
27
|
+
) -> SlotStatus:
|
|
28
|
+
"""Determine the status of a worktree slot.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
slot_name: The slot identifier (e.g., "erk-managed-wt-01")
|
|
32
|
+
assigned_slots: Set of slot names that have assignments in pool.json
|
|
33
|
+
reason: The reason code from _get_slot_reason (indicates any problems)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Status: "assigned" (healthy assignment), "error" (assignment with problem),
|
|
37
|
+
"available" (can be used)
|
|
38
|
+
"""
|
|
39
|
+
# If slot has an assignment in pool.json
|
|
40
|
+
if slot_name in assigned_slots:
|
|
41
|
+
# Check if there's a problem with the assignment
|
|
42
|
+
if reason != "-":
|
|
43
|
+
return "error"
|
|
44
|
+
return "assigned"
|
|
45
|
+
|
|
46
|
+
# No assignment - slot is available
|
|
47
|
+
return "available"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_slot_reason(
|
|
51
|
+
assigned_branch: str | None,
|
|
52
|
+
actual_branch: str | None,
|
|
53
|
+
) -> SlotReason:
|
|
54
|
+
"""Determine the reason for slot state.
|
|
55
|
+
|
|
56
|
+
Returns a kebab-case reason explaining any issues with the slot:
|
|
57
|
+
- "worktree-missing": pool.json has assignment but worktree doesn't exist
|
|
58
|
+
- "branch-mismatch": worktree exists but has different branch than assignment
|
|
59
|
+
- "-": healthy state (no problem)
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
assigned_branch: Branch assigned in pool.json (if any)
|
|
63
|
+
actual_branch: Actual branch on filesystem (if any)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Reason literal explaining any issues
|
|
67
|
+
"""
|
|
68
|
+
if actual_branch is None:
|
|
69
|
+
# No worktree on filesystem
|
|
70
|
+
if assigned_branch is not None:
|
|
71
|
+
return "worktree-missing" # pool.json says assigned but no worktree
|
|
72
|
+
return "-" # Neither assigned nor exists - healthy available state
|
|
73
|
+
|
|
74
|
+
if assigned_branch is None:
|
|
75
|
+
# Worktree exists but not assigned - healthy available state
|
|
76
|
+
return "-"
|
|
77
|
+
|
|
78
|
+
# Both exist - check if they match
|
|
79
|
+
if actual_branch == assigned_branch:
|
|
80
|
+
return "-" # Healthy - branches match
|
|
81
|
+
return "branch-mismatch"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@alias("ls")
|
|
85
|
+
@click.command("list")
|
|
86
|
+
@click.pass_obj
|
|
87
|
+
def slot_list(ctx: ErkContext) -> None:
|
|
88
|
+
"""List all pool slots with unified status view.
|
|
89
|
+
|
|
90
|
+
Shows a table combining pool.json state and filesystem state:
|
|
91
|
+
- Worktree: The pool worktree name
|
|
92
|
+
- Branch: Assigned branch or - (available)
|
|
93
|
+
- Assigned: When the assignment was made (relative time)
|
|
94
|
+
- Status: available, assigned (healthy), or error (has problem)
|
|
95
|
+
- Reason: worktree-missing, branch-mismatch, or - (healthy)
|
|
96
|
+
"""
|
|
97
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
98
|
+
|
|
99
|
+
# Load pool state (or use defaults if no state exists)
|
|
100
|
+
state = load_pool_state(repo.pool_json_path)
|
|
101
|
+
if state is None:
|
|
102
|
+
state = PoolState(
|
|
103
|
+
version="1.0",
|
|
104
|
+
pool_size=DEFAULT_POOL_SIZE,
|
|
105
|
+
slots=(),
|
|
106
|
+
assignments=(),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Build lookup set
|
|
110
|
+
assigned_slots = {a.slot_name for a in state.assignments}
|
|
111
|
+
|
|
112
|
+
# Build lookup of slot_name -> (branch_name, relative_time)
|
|
113
|
+
assignments_by_slot: dict[str, tuple[str, str]] = {}
|
|
114
|
+
for assignment in state.assignments:
|
|
115
|
+
relative_time = format_relative_time(assignment.assigned_at)
|
|
116
|
+
assignments_by_slot[assignment.slot_name] = (assignment.branch_name, relative_time)
|
|
117
|
+
|
|
118
|
+
# Build lookup of slot_name -> last_objective_issue
|
|
119
|
+
objectives_by_slot: dict[str, int] = {}
|
|
120
|
+
for slot in state.slots:
|
|
121
|
+
if slot.last_objective_issue is not None:
|
|
122
|
+
objectives_by_slot[slot.name] = slot.last_objective_issue
|
|
123
|
+
|
|
124
|
+
# Create Rich table
|
|
125
|
+
table = Table(show_header=True, header_style="bold", box=None)
|
|
126
|
+
table.add_column("Worktree", style="cyan", no_wrap=True)
|
|
127
|
+
table.add_column("Branch", style="yellow", no_wrap=True)
|
|
128
|
+
table.add_column("Objective", no_wrap=True)
|
|
129
|
+
table.add_column("Assigned", no_wrap=True)
|
|
130
|
+
table.add_column("Status", no_wrap=True)
|
|
131
|
+
table.add_column("Reason", no_wrap=True)
|
|
132
|
+
table.add_column("Changes", no_wrap=True)
|
|
133
|
+
|
|
134
|
+
# Track counts for summary
|
|
135
|
+
assigned_count = 0
|
|
136
|
+
error_count = 0
|
|
137
|
+
|
|
138
|
+
# Add rows for all slots
|
|
139
|
+
for slot_num in range(1, state.pool_size + 1):
|
|
140
|
+
slot_name = generate_slot_name(slot_num)
|
|
141
|
+
worktree_path = repo.worktrees_dir / slot_name
|
|
142
|
+
|
|
143
|
+
# Check if worktree exists and get current branch
|
|
144
|
+
worktree_exists = ctx.git.path_exists(worktree_path)
|
|
145
|
+
|
|
146
|
+
actual_branch: str | None = None
|
|
147
|
+
if worktree_exists:
|
|
148
|
+
actual_branch = ctx.git.get_current_branch(worktree_path)
|
|
149
|
+
|
|
150
|
+
# Get assigned branch info
|
|
151
|
+
assigned_branch: str | None = None
|
|
152
|
+
assigned_time = "-"
|
|
153
|
+
if slot_name in assignments_by_slot:
|
|
154
|
+
assigned_branch, assigned_time = assignments_by_slot[slot_name]
|
|
155
|
+
|
|
156
|
+
# Determine reason for any issues (needed before status)
|
|
157
|
+
reason = _get_slot_reason(assigned_branch, actual_branch)
|
|
158
|
+
|
|
159
|
+
# Determine status (depends on reason)
|
|
160
|
+
status = _determine_slot_status(slot_name, assigned_slots, reason)
|
|
161
|
+
|
|
162
|
+
# Format branch display
|
|
163
|
+
if status == "available":
|
|
164
|
+
branch_display = "[dim]-[/dim]"
|
|
165
|
+
assigned_time = "-"
|
|
166
|
+
else:
|
|
167
|
+
# Both "assigned" and "error" show the assigned branch
|
|
168
|
+
branch_display = assigned_branch if assigned_branch else "-"
|
|
169
|
+
|
|
170
|
+
# Format status with color
|
|
171
|
+
status_map: dict[SlotStatus, str] = {
|
|
172
|
+
"available": "[dim]available[/dim]",
|
|
173
|
+
"assigned": "[green]assigned[/green]",
|
|
174
|
+
"error": "[red]error[/red]",
|
|
175
|
+
}
|
|
176
|
+
status_display = status_map[status]
|
|
177
|
+
|
|
178
|
+
# Format reason with color (only shown for error states)
|
|
179
|
+
reason_map: dict[SlotReason, str] = {
|
|
180
|
+
"worktree-missing": "[red]worktree-missing[/red]",
|
|
181
|
+
"branch-mismatch": "[red]branch-mismatch[/red]",
|
|
182
|
+
"-": "[dim]-[/dim]",
|
|
183
|
+
}
|
|
184
|
+
reason_display = reason_map[reason]
|
|
185
|
+
|
|
186
|
+
# Format changes display
|
|
187
|
+
changes_display: str
|
|
188
|
+
if worktree_exists and ctx.git.has_uncommitted_changes(worktree_path):
|
|
189
|
+
changes_display = "[yellow]dirty[/yellow]"
|
|
190
|
+
else:
|
|
191
|
+
changes_display = "[dim]-[/dim]"
|
|
192
|
+
|
|
193
|
+
# Format objective display
|
|
194
|
+
objective_display: str
|
|
195
|
+
if slot_name in objectives_by_slot:
|
|
196
|
+
objective_display = f"#{objectives_by_slot[slot_name]}"
|
|
197
|
+
else:
|
|
198
|
+
objective_display = "[dim]-[/dim]"
|
|
199
|
+
|
|
200
|
+
table.add_row(
|
|
201
|
+
slot_name,
|
|
202
|
+
branch_display,
|
|
203
|
+
objective_display,
|
|
204
|
+
assigned_time,
|
|
205
|
+
status_display,
|
|
206
|
+
reason_display,
|
|
207
|
+
changes_display,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Track counts
|
|
211
|
+
if status == "assigned":
|
|
212
|
+
assigned_count += 1
|
|
213
|
+
elif status == "error":
|
|
214
|
+
error_count += 1
|
|
215
|
+
|
|
216
|
+
# Output table to stderr (consistent with user_output convention)
|
|
217
|
+
# Use width=200 to ensure proper display without truncation
|
|
218
|
+
console = Console(stderr=True, width=200, force_terminal=True)
|
|
219
|
+
console.print(table)
|
|
220
|
+
|
|
221
|
+
# Print summary
|
|
222
|
+
available_count = state.pool_size - assigned_count - error_count
|
|
223
|
+
console.print(
|
|
224
|
+
f"\nPool: {state.pool_size} slots | "
|
|
225
|
+
f"{available_count} available | "
|
|
226
|
+
f"{assigned_count} assigned | "
|
|
227
|
+
f"{error_count} error"
|
|
228
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Slot repair command - remove stale assignments from pool state."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from erk.cli.commands.slot.check_cmd import SyncIssue, run_sync_diagnostics
|
|
8
|
+
from erk.cli.core import discover_repo_context
|
|
9
|
+
from erk.core.context import ErkContext
|
|
10
|
+
from erk.core.worktree_pool import (
|
|
11
|
+
PoolState,
|
|
12
|
+
SlotAssignment,
|
|
13
|
+
load_pool_state,
|
|
14
|
+
save_pool_state,
|
|
15
|
+
)
|
|
16
|
+
from erk_shared.output.output import user_confirm, user_output
|
|
17
|
+
|
|
18
|
+
# Issue codes that can be auto-repaired by removing the assignment
|
|
19
|
+
AUTO_REPAIRABLE_CODES = frozenset({"orphan-state"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _extract_slot_name(issue: SyncIssue) -> str:
|
|
23
|
+
"""Extract slot name from issue message.
|
|
24
|
+
|
|
25
|
+
Issue messages have format: "Slot <slot-name>: <description>"
|
|
26
|
+
"""
|
|
27
|
+
return issue.message.split(":")[0].replace("Slot ", "")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _format_remediation(issue: SyncIssue, worktrees_dir: Path) -> list[str]:
|
|
31
|
+
"""Format remediation suggestions for an issue.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
issue: The sync issue
|
|
35
|
+
worktrees_dir: Path to worktrees directory (for path display)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
List of remediation command strings
|
|
39
|
+
"""
|
|
40
|
+
slot_name = _extract_slot_name(issue)
|
|
41
|
+
worktree_path = worktrees_dir / slot_name
|
|
42
|
+
|
|
43
|
+
if issue.code == "branch-mismatch":
|
|
44
|
+
# Extract expected branch from message: "pool says 'X', git says 'Y'"
|
|
45
|
+
# Message format: "Slot <slot>: pool says '<expected>', git says '<actual>'"
|
|
46
|
+
parts = issue.message.split("pool says '")
|
|
47
|
+
expected_branch = parts[1].split("'")[0] if len(parts) > 1 else "<expected-branch>"
|
|
48
|
+
return [
|
|
49
|
+
f"erk slot unassign {slot_name}",
|
|
50
|
+
f"cd {worktree_path} && git checkout {expected_branch}",
|
|
51
|
+
]
|
|
52
|
+
elif issue.code == "missing-branch":
|
|
53
|
+
return [f"erk slot unassign {slot_name}"]
|
|
54
|
+
elif issue.code == "git-registry-missing":
|
|
55
|
+
return [f"erk slot unassign {slot_name}"]
|
|
56
|
+
else:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_stale_assignments(
|
|
61
|
+
state: PoolState,
|
|
62
|
+
issues: list[SyncIssue],
|
|
63
|
+
) -> list[SlotAssignment]:
|
|
64
|
+
"""Find assignments that can be auto-repaired.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
state: Pool state to check
|
|
68
|
+
issues: List of sync issues from run_sync_diagnostics
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of stale SlotAssignments (orphan-state issues)
|
|
72
|
+
"""
|
|
73
|
+
# Collect the slot names that have auto-repairable issues
|
|
74
|
+
stale_slot_names = {
|
|
75
|
+
_extract_slot_name(issue) for issue in issues if issue.code in AUTO_REPAIRABLE_CODES
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Return the actual assignments that are stale
|
|
79
|
+
return [a for a in state.assignments if a.slot_name in stale_slot_names]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def execute_repair(
|
|
83
|
+
state: PoolState,
|
|
84
|
+
stale_assignments: list[SlotAssignment],
|
|
85
|
+
) -> PoolState:
|
|
86
|
+
"""Create new pool state with stale assignments removed.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
state: Current pool state
|
|
90
|
+
stale_assignments: Assignments to remove
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
New PoolState with stale assignments filtered out
|
|
94
|
+
"""
|
|
95
|
+
stale_slot_names = {a.slot_name for a in stale_assignments}
|
|
96
|
+
new_assignments = tuple(a for a in state.assignments if a.slot_name not in stale_slot_names)
|
|
97
|
+
|
|
98
|
+
return PoolState(
|
|
99
|
+
version=state.version,
|
|
100
|
+
pool_size=state.pool_size,
|
|
101
|
+
slots=state.slots,
|
|
102
|
+
assignments=new_assignments,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _display_informational_issues(
|
|
107
|
+
issues: list[SyncIssue],
|
|
108
|
+
worktrees_dir: Path,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Display informational issues that require manual intervention.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
issues: List of non-auto-repairable issues
|
|
114
|
+
worktrees_dir: Path to worktrees directory (for path display)
|
|
115
|
+
"""
|
|
116
|
+
informational = [i for i in issues if i.code not in AUTO_REPAIRABLE_CODES]
|
|
117
|
+
if not informational:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
user_output("")
|
|
121
|
+
user_output(f"Found {len(informational)} issue(s) requiring manual intervention:")
|
|
122
|
+
for issue in informational:
|
|
123
|
+
user_output(f" [{click.style(issue.code, fg='yellow')}] {issue.message}")
|
|
124
|
+
remediation = _format_remediation(issue, worktrees_dir)
|
|
125
|
+
if remediation:
|
|
126
|
+
user_output(" Remediation:")
|
|
127
|
+
for cmd in remediation:
|
|
128
|
+
user_output(f" {click.style(cmd, fg='cyan')}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@click.command("repair")
|
|
132
|
+
@click.option("-f", "--force", is_flag=True, help="Skip confirmation prompt")
|
|
133
|
+
@click.pass_obj
|
|
134
|
+
def slot_repair(ctx: ErkContext, force: bool) -> None:
|
|
135
|
+
"""Remove stale assignments from pool state.
|
|
136
|
+
|
|
137
|
+
Finds assignments where the worktree directory no longer exists
|
|
138
|
+
and removes them from pool.json.
|
|
139
|
+
|
|
140
|
+
Also displays other issues (like branch-mismatch) that require
|
|
141
|
+
manual intervention with suggested remediation commands.
|
|
142
|
+
|
|
143
|
+
Use --force to skip the confirmation prompt.
|
|
144
|
+
"""
|
|
145
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
146
|
+
|
|
147
|
+
# Load pool state
|
|
148
|
+
state = load_pool_state(repo.pool_json_path)
|
|
149
|
+
if state is None:
|
|
150
|
+
user_output("Error: No pool configured. Run `erk slot create` first.")
|
|
151
|
+
raise SystemExit(1) from None
|
|
152
|
+
|
|
153
|
+
# Run full diagnostics to get all issues
|
|
154
|
+
all_issues = run_sync_diagnostics(ctx, state, repo.root)
|
|
155
|
+
|
|
156
|
+
# Find stale (auto-repairable) assignments
|
|
157
|
+
stale_assignments = find_stale_assignments(state, all_issues)
|
|
158
|
+
|
|
159
|
+
# Display informational issues (non-auto-repairable)
|
|
160
|
+
_display_informational_issues(all_issues, repo.worktrees_dir)
|
|
161
|
+
|
|
162
|
+
if not stale_assignments:
|
|
163
|
+
if not any(i.code not in AUTO_REPAIRABLE_CODES for i in all_issues):
|
|
164
|
+
user_output(click.style("✓ No issues found", fg="green"))
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Show what will be repaired
|
|
168
|
+
user_output("")
|
|
169
|
+
user_output(f"Found {len(stale_assignments)} repairable issue(s):")
|
|
170
|
+
for assignment in stale_assignments:
|
|
171
|
+
user_output(
|
|
172
|
+
f" - {click.style(assignment.slot_name, fg='cyan')}: "
|
|
173
|
+
f"branch '{click.style(assignment.branch_name, fg='yellow')}' "
|
|
174
|
+
f"(worktree missing)"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Prompt for confirmation unless --force
|
|
178
|
+
if not force:
|
|
179
|
+
if not user_confirm("\nRemove these stale assignments?", default=True):
|
|
180
|
+
user_output("Aborted.")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# Execute repair
|
|
184
|
+
new_state = execute_repair(state, stale_assignments)
|
|
185
|
+
save_pool_state(repo.pool_json_path, new_state)
|
|
186
|
+
|
|
187
|
+
user_output("")
|
|
188
|
+
user_output(
|
|
189
|
+
click.style("✓ ", fg="green") + f"Removed {len(stale_assignments)} stale assignment(s)"
|
|
190
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Stack operation commands for managing worktree stacks."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.alias import register_with_aliases
|
|
6
|
+
from erk.cli.commands.stack.consolidate_cmd import consolidate_stack
|
|
7
|
+
from erk.cli.commands.stack.list_cmd import list_stack
|
|
8
|
+
from erk.cli.commands.stack.move_cmd import move_stack
|
|
9
|
+
from erk.cli.commands.stack.split_old.command import split_cmd as split_stack
|
|
10
|
+
from erk.cli.graphite_command import GraphiteGroup
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group("stack", cls=GraphiteGroup)
|
|
14
|
+
def stack_group() -> None:
|
|
15
|
+
"""Manage worktree stack operations."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Register subcommands
|
|
20
|
+
stack_group.add_command(consolidate_stack, name="consolidate")
|
|
21
|
+
register_with_aliases(stack_group, list_stack, name="list")
|
|
22
|
+
stack_group.add_command(move_stack, name="move")
|
|
23
|
+
stack_group.add_command(split_stack, name="split")
|