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,121 @@
|
|
|
1
|
+
"""Sync agent documentation index files.
|
|
2
|
+
|
|
3
|
+
This command generates index.md files for docs/learned/ from frontmatter metadata.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from erk.agent_docs.operations import sync_agent_docs
|
|
11
|
+
from erk.cli.subprocess_utils import run_with_error_reporting
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command(name="sync")
|
|
15
|
+
@click.option(
|
|
16
|
+
"--dry-run",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
help="Show what would be done without writing files.",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--check",
|
|
22
|
+
is_flag=True,
|
|
23
|
+
help="Check if files are in sync without writing. Exit 1 if changes needed.",
|
|
24
|
+
)
|
|
25
|
+
def sync_command(*, dry_run: bool, check: bool) -> None:
|
|
26
|
+
"""Regenerate index files from frontmatter.
|
|
27
|
+
|
|
28
|
+
Generates index.md files for:
|
|
29
|
+
- docs/learned/index.md (root index with categories and uncategorized docs)
|
|
30
|
+
- docs/learned/<category>/index.md (for categories with 2+ docs)
|
|
31
|
+
|
|
32
|
+
Index files are auto-generated and should not be manually edited.
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
- 0: Sync completed successfully (or --check passes)
|
|
36
|
+
- 1: Error during sync (or --check finds files out of sync)
|
|
37
|
+
"""
|
|
38
|
+
# --check implies dry-run behavior
|
|
39
|
+
effective_dry_run = dry_run or check
|
|
40
|
+
|
|
41
|
+
# Find repository root
|
|
42
|
+
result = run_with_error_reporting(
|
|
43
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
44
|
+
error_prefix="Failed to find repository root",
|
|
45
|
+
troubleshooting=["Ensure you're running from within a git repository"],
|
|
46
|
+
)
|
|
47
|
+
project_root = Path(result.stdout.strip())
|
|
48
|
+
|
|
49
|
+
if not project_root.exists():
|
|
50
|
+
click.echo(click.style("Error: Repository root not found", fg="red"), err=True)
|
|
51
|
+
raise SystemExit(1)
|
|
52
|
+
|
|
53
|
+
agent_docs_dir = project_root / "docs" / "learned"
|
|
54
|
+
if not agent_docs_dir.exists():
|
|
55
|
+
click.echo(click.style("No docs/learned/ directory found", fg="cyan"), err=True)
|
|
56
|
+
raise SystemExit(0)
|
|
57
|
+
|
|
58
|
+
# Sync index files
|
|
59
|
+
sync_result = sync_agent_docs(project_root, dry_run=effective_dry_run)
|
|
60
|
+
|
|
61
|
+
# Report results
|
|
62
|
+
if effective_dry_run:
|
|
63
|
+
click.echo(click.style("Dry run - no files written", fg="cyan", bold=True), err=True)
|
|
64
|
+
click.echo(err=True)
|
|
65
|
+
|
|
66
|
+
total_changes = len(sync_result.created) + len(sync_result.updated)
|
|
67
|
+
|
|
68
|
+
if sync_result.created:
|
|
69
|
+
action = "Would create" if effective_dry_run else "Created"
|
|
70
|
+
click.echo(f"{action} {len(sync_result.created)} file(s):", err=True)
|
|
71
|
+
for path in sync_result.created:
|
|
72
|
+
click.echo(f" + {path}", err=True)
|
|
73
|
+
click.echo(err=True)
|
|
74
|
+
|
|
75
|
+
if sync_result.updated:
|
|
76
|
+
action = "Would update" if effective_dry_run else "Updated"
|
|
77
|
+
click.echo(f"{action} {len(sync_result.updated)} file(s):", err=True)
|
|
78
|
+
for path in sync_result.updated:
|
|
79
|
+
click.echo(f" ~ {path}", err=True)
|
|
80
|
+
click.echo(err=True)
|
|
81
|
+
|
|
82
|
+
if sync_result.unchanged:
|
|
83
|
+
click.echo(f"Unchanged: {len(sync_result.unchanged)} file(s)", err=True)
|
|
84
|
+
click.echo(err=True)
|
|
85
|
+
|
|
86
|
+
# Report tripwires
|
|
87
|
+
if sync_result.tripwires_count > 0:
|
|
88
|
+
click.echo(f"Tripwires: {sync_result.tripwires_count} collected", err=True)
|
|
89
|
+
click.echo(err=True)
|
|
90
|
+
|
|
91
|
+
if sync_result.skipped_invalid > 0:
|
|
92
|
+
click.echo(
|
|
93
|
+
click.style(
|
|
94
|
+
f"Skipped {sync_result.skipped_invalid} doc(s) with invalid frontmatter",
|
|
95
|
+
fg="yellow",
|
|
96
|
+
),
|
|
97
|
+
err=True,
|
|
98
|
+
)
|
|
99
|
+
click.echo(" Run 'erk docs validate' to see errors", err=True)
|
|
100
|
+
click.echo(err=True)
|
|
101
|
+
|
|
102
|
+
# Summary
|
|
103
|
+
if total_changes == 0 and sync_result.skipped_invalid == 0:
|
|
104
|
+
click.echo(click.style("All files are up to date", fg="green"), err=True)
|
|
105
|
+
elif total_changes > 0:
|
|
106
|
+
if check:
|
|
107
|
+
msg = f"Files out of sync: {total_changes} change(s) needed"
|
|
108
|
+
click.echo(click.style(msg, fg="red", bold=True), err=True)
|
|
109
|
+
click.echo(err=True)
|
|
110
|
+
click.echo("Run 'erk docs sync' to regenerate files from frontmatter.", err=True)
|
|
111
|
+
raise SystemExit(1)
|
|
112
|
+
elif effective_dry_run:
|
|
113
|
+
click.echo(
|
|
114
|
+
click.style(f"Would make {total_changes} change(s)", fg="cyan", bold=True),
|
|
115
|
+
err=True,
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
click.echo(
|
|
119
|
+
click.style(f"Sync complete: {total_changes} change(s)", fg="green"),
|
|
120
|
+
err=True,
|
|
121
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Validate agent documentation frontmatter.
|
|
2
|
+
|
|
3
|
+
This command validates that all markdown files in docs/learned/ have valid
|
|
4
|
+
frontmatter with required fields: title and read_when.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from erk.agent_docs.operations import validate_agent_docs
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command(name="validate")
|
|
16
|
+
@click.option(
|
|
17
|
+
"--verbose",
|
|
18
|
+
"-v",
|
|
19
|
+
is_flag=True,
|
|
20
|
+
help="Show details for all files, not just errors.",
|
|
21
|
+
)
|
|
22
|
+
def validate_command(*, verbose: bool) -> None:
|
|
23
|
+
"""Validate agent documentation frontmatter.
|
|
24
|
+
|
|
25
|
+
Checks that all markdown files in docs/learned/ have valid frontmatter:
|
|
26
|
+
- title: Human-readable document title
|
|
27
|
+
- read_when: List of conditions when agent should read this doc
|
|
28
|
+
|
|
29
|
+
Index files (index.md) are skipped as they are auto-generated.
|
|
30
|
+
|
|
31
|
+
Exit codes:
|
|
32
|
+
- 0: All files are valid
|
|
33
|
+
- 1: Validation errors found
|
|
34
|
+
"""
|
|
35
|
+
# Find repository root
|
|
36
|
+
result = subprocess.run(
|
|
37
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
38
|
+
check=True,
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
)
|
|
42
|
+
project_root = Path(result.stdout.strip())
|
|
43
|
+
|
|
44
|
+
if not project_root.exists():
|
|
45
|
+
click.echo(click.style("Error: Repository root not found", fg="red"), err=True)
|
|
46
|
+
raise SystemExit(1)
|
|
47
|
+
|
|
48
|
+
agent_docs_dir = project_root / "docs" / "learned"
|
|
49
|
+
if not agent_docs_dir.exists():
|
|
50
|
+
click.echo(click.style("No docs/learned/ directory found", fg="cyan"), err=True)
|
|
51
|
+
raise SystemExit(0)
|
|
52
|
+
|
|
53
|
+
# Validate all files
|
|
54
|
+
results = validate_agent_docs(project_root)
|
|
55
|
+
|
|
56
|
+
if len(results) == 0:
|
|
57
|
+
click.echo(click.style("No agent documentation files found", fg="cyan"), err=True)
|
|
58
|
+
raise SystemExit(0)
|
|
59
|
+
|
|
60
|
+
valid_count = sum(1 for r in results if r.is_valid)
|
|
61
|
+
invalid_count = len(results) - valid_count
|
|
62
|
+
|
|
63
|
+
# Show results
|
|
64
|
+
if verbose or invalid_count > 0:
|
|
65
|
+
for validation_result in results:
|
|
66
|
+
if validation_result.is_valid:
|
|
67
|
+
if verbose:
|
|
68
|
+
status = click.style("OK", fg="green")
|
|
69
|
+
click.echo(f"{status} {validation_result.file_path}", err=True)
|
|
70
|
+
else:
|
|
71
|
+
status = click.style("FAIL", fg="red")
|
|
72
|
+
click.echo(f"{status} {validation_result.file_path}", err=True)
|
|
73
|
+
for error in validation_result.errors:
|
|
74
|
+
click.echo(f" {error}", err=True)
|
|
75
|
+
|
|
76
|
+
# Summary
|
|
77
|
+
click.echo(err=True)
|
|
78
|
+
if invalid_count == 0:
|
|
79
|
+
click.echo(
|
|
80
|
+
click.style("Agent docs validation: PASSED", fg="green", bold=True),
|
|
81
|
+
err=True,
|
|
82
|
+
)
|
|
83
|
+
click.echo(err=True)
|
|
84
|
+
click.echo(f"Files validated: {len(results)}", err=True)
|
|
85
|
+
click.echo("All files have valid frontmatter!", err=True)
|
|
86
|
+
else:
|
|
87
|
+
click.echo(
|
|
88
|
+
click.style("Agent docs validation: FAILED", fg="red", bold=True),
|
|
89
|
+
err=True,
|
|
90
|
+
)
|
|
91
|
+
click.echo(err=True)
|
|
92
|
+
click.echo(f"Files validated: {len(results)}", err=True)
|
|
93
|
+
click.echo(f" Valid: {valid_count}", err=True)
|
|
94
|
+
click.echo(f" Invalid: {invalid_count}", err=True)
|
|
95
|
+
click.echo(err=True)
|
|
96
|
+
click.echo("Required frontmatter format:", err=True)
|
|
97
|
+
click.echo(" ---", err=True)
|
|
98
|
+
click.echo(" title: Document Title", err=True)
|
|
99
|
+
click.echo(" read_when:", err=True)
|
|
100
|
+
click.echo(' - "when to read this doc"', err=True)
|
|
101
|
+
click.echo(" ---", err=True)
|
|
102
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Doctor command for erk setup diagnostics.
|
|
2
|
+
|
|
3
|
+
Runs health checks on the erk setup to identify issues with
|
|
4
|
+
CLI availability, repository configuration, and Claude settings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from erk.core.context import ErkContext
|
|
10
|
+
from erk.core.health_checks import CheckResult, run_all_checks
|
|
11
|
+
from erk.core.health_checks_dogfooder import EARLY_DOGFOODER_CHECK_NAMES
|
|
12
|
+
from erk_shared.hooks.logging import clear_hook_logs
|
|
13
|
+
|
|
14
|
+
# Sub-group definitions for Repository Setup condensed display
|
|
15
|
+
REPO_SUBGROUPS: dict[str, set[str]] = {
|
|
16
|
+
"Git repository": {"repository", "gitignore"},
|
|
17
|
+
"Claude settings": {
|
|
18
|
+
"claude-erk-permission",
|
|
19
|
+
"claude-settings",
|
|
20
|
+
"user-prompt-hook",
|
|
21
|
+
"exit-plan-hook",
|
|
22
|
+
},
|
|
23
|
+
"Erk configuration": {
|
|
24
|
+
"required-version",
|
|
25
|
+
"legacy-prompt-hooks",
|
|
26
|
+
"legacy-config",
|
|
27
|
+
"managed-artifacts",
|
|
28
|
+
"post-plan-implement-ci-hook",
|
|
29
|
+
},
|
|
30
|
+
"GitHub": {"workflow-permissions"},
|
|
31
|
+
"Hooks": {"hooks"},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Sub-group definitions for User Setup condensed display
|
|
35
|
+
USER_SUBGROUPS: dict[str, set[str]] = {
|
|
36
|
+
"User checks": {"github-auth", "claude-hooks", "statusline", "shell-integration"},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _format_check_result(result: CheckResult, indent: str = "", verbose: bool = False) -> None:
|
|
41
|
+
"""Format and display a single check result.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
result: The check result to format
|
|
45
|
+
indent: Optional indentation prefix for nested display
|
|
46
|
+
verbose: If True and verbose_details exists, use it instead of details
|
|
47
|
+
"""
|
|
48
|
+
if not result.passed:
|
|
49
|
+
icon = click.style("❌", fg="red")
|
|
50
|
+
elif result.warning:
|
|
51
|
+
icon = click.style("⚠️", fg="yellow")
|
|
52
|
+
elif result.info:
|
|
53
|
+
icon = click.style("ℹ️", fg="cyan")
|
|
54
|
+
else:
|
|
55
|
+
icon = click.style("✅", fg="green")
|
|
56
|
+
|
|
57
|
+
# Use verbose_details in verbose mode if available, otherwise use details
|
|
58
|
+
details = result.verbose_details if verbose and result.verbose_details else result.details
|
|
59
|
+
|
|
60
|
+
if details and "\n" not in details:
|
|
61
|
+
# Single-line details: show inline
|
|
62
|
+
styled_details = click.style(f" - {details}", dim=True)
|
|
63
|
+
click.echo(f"{indent}{icon} {result.message}{styled_details}")
|
|
64
|
+
else:
|
|
65
|
+
click.echo(f"{indent}{icon} {result.message}")
|
|
66
|
+
if details:
|
|
67
|
+
# Multi-line details: show with indentation
|
|
68
|
+
for line in details.split("\n"):
|
|
69
|
+
click.echo(click.style(f"{indent} {line}", dim=True))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _format_subgroup(name: str, checks: list[CheckResult], verbose: bool, indent: str = "") -> None:
|
|
73
|
+
"""Format a sub-group of checks (condensed or expanded).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
name: Sub-group display name
|
|
77
|
+
checks: List of check results in this sub-group
|
|
78
|
+
verbose: If True, always show all individual checks
|
|
79
|
+
indent: Indentation prefix
|
|
80
|
+
"""
|
|
81
|
+
if not checks:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
passed = sum(1 for c in checks if c.passed)
|
|
85
|
+
total = len(checks)
|
|
86
|
+
all_passed = passed == total
|
|
87
|
+
|
|
88
|
+
if verbose:
|
|
89
|
+
# Always show all individual checks with sub-group header
|
|
90
|
+
click.echo(click.style(f"{indent} {name}", dim=True))
|
|
91
|
+
for result in checks:
|
|
92
|
+
_format_check_result(result, indent=f"{indent} ", verbose=True)
|
|
93
|
+
elif all_passed:
|
|
94
|
+
# Condensed: single line with count
|
|
95
|
+
icon = click.style("✅", fg="green")
|
|
96
|
+
click.echo(f"{indent}{icon} {name} ({total} checks)")
|
|
97
|
+
else:
|
|
98
|
+
# Failed: show summary line + expand failures
|
|
99
|
+
icon = click.style("❌", fg="red")
|
|
100
|
+
click.echo(f"{indent}{icon} {name} ({passed}/{total} checks)")
|
|
101
|
+
for result in checks:
|
|
102
|
+
if not result.passed:
|
|
103
|
+
_format_check_result(result, indent=f"{indent} ", verbose=False)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@click.command("doctor")
|
|
107
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show all individual checks")
|
|
108
|
+
@click.option("--dogfooder", is_flag=True, help="Include early dogfooder migration checks")
|
|
109
|
+
@click.option(
|
|
110
|
+
"--clear-hook-logs", "clear_hook_logs_flag", is_flag=True, help="Clear all hook execution logs"
|
|
111
|
+
)
|
|
112
|
+
@click.pass_obj
|
|
113
|
+
def doctor_cmd(ctx: ErkContext, verbose: bool, dogfooder: bool, clear_hook_logs_flag: bool) -> None:
|
|
114
|
+
"""Run diagnostic checks on erk setup.
|
|
115
|
+
|
|
116
|
+
Checks for:
|
|
117
|
+
|
|
118
|
+
\b
|
|
119
|
+
- Repository Setup: git config, Claude settings, erk config, hooks
|
|
120
|
+
- User Setup: prerequisites (erk, claude, gt, gh, uv), GitHub auth
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
|
|
124
|
+
\b
|
|
125
|
+
# Run checks (condensed output)
|
|
126
|
+
erk doctor
|
|
127
|
+
|
|
128
|
+
# Show all individual checks
|
|
129
|
+
erk doctor --verbose
|
|
130
|
+
|
|
131
|
+
# Include early dogfooder migration checks
|
|
132
|
+
erk doctor --dogfooder
|
|
133
|
+
|
|
134
|
+
# Clear hook execution logs
|
|
135
|
+
erk doctor --clear-hook-logs
|
|
136
|
+
"""
|
|
137
|
+
# Handle --clear-hook-logs flag (clears logs and returns early)
|
|
138
|
+
if clear_hook_logs_flag:
|
|
139
|
+
deleted_count = clear_hook_logs(ctx.repo_root)
|
|
140
|
+
click.echo(f"Cleared {deleted_count} hook log(s)")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
click.echo(click.style("🔍 Checking erk setup...", bold=True))
|
|
144
|
+
click.echo("")
|
|
145
|
+
|
|
146
|
+
# Run all checks
|
|
147
|
+
results = run_all_checks(ctx)
|
|
148
|
+
|
|
149
|
+
# Group results by category
|
|
150
|
+
prerequisite_names = {"erk", "claude", "graphite", "github", "uv"}
|
|
151
|
+
user_check_names = {"github-auth", "claude-hooks", "statusline", "shell-integration"}
|
|
152
|
+
repo_check_names = {
|
|
153
|
+
"repository",
|
|
154
|
+
"claude-settings",
|
|
155
|
+
"user-prompt-hook",
|
|
156
|
+
"exit-plan-hook",
|
|
157
|
+
"gitignore",
|
|
158
|
+
"claude-erk-permission",
|
|
159
|
+
"legacy-config",
|
|
160
|
+
"required-version",
|
|
161
|
+
"legacy-prompt-hooks",
|
|
162
|
+
"managed-artifacts",
|
|
163
|
+
"post-plan-implement-ci-hook",
|
|
164
|
+
"workflow-permissions",
|
|
165
|
+
"hooks",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
prerequisite_checks = [r for r in results if r.name in prerequisite_names]
|
|
169
|
+
user_checks = [r for r in results if r.name in user_check_names]
|
|
170
|
+
repo_checks = [r for r in results if r.name in repo_check_names]
|
|
171
|
+
early_dogfooder_checks = [r for r in results if r.name in EARLY_DOGFOODER_CHECK_NAMES]
|
|
172
|
+
|
|
173
|
+
# Track displayed check names to catch any uncategorized checks
|
|
174
|
+
displayed_names = (
|
|
175
|
+
prerequisite_names | user_check_names | repo_check_names | EARLY_DOGFOODER_CHECK_NAMES
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Display Repository Setup FIRST (with sub-groups)
|
|
179
|
+
click.echo(click.style("Repository Setup", bold=True))
|
|
180
|
+
if verbose:
|
|
181
|
+
# In verbose mode, show sub-groups with all individual checks
|
|
182
|
+
for subgroup_name, subgroup_check_names in REPO_SUBGROUPS.items():
|
|
183
|
+
subgroup_checks = [r for r in repo_checks if r.name in subgroup_check_names]
|
|
184
|
+
_format_subgroup(subgroup_name, subgroup_checks, verbose=True)
|
|
185
|
+
else:
|
|
186
|
+
# Condensed mode: show sub-group summaries
|
|
187
|
+
for subgroup_name, subgroup_check_names in REPO_SUBGROUPS.items():
|
|
188
|
+
subgroup_checks = [r for r in repo_checks if r.name in subgroup_check_names]
|
|
189
|
+
_format_subgroup(subgroup_name, subgroup_checks, verbose=False)
|
|
190
|
+
click.echo("")
|
|
191
|
+
|
|
192
|
+
# Display Early Dogfooder checks (only when --dogfooder flag is passed)
|
|
193
|
+
if dogfooder and early_dogfooder_checks:
|
|
194
|
+
click.echo(click.style("Early Dogfooder", bold=True))
|
|
195
|
+
for result in early_dogfooder_checks:
|
|
196
|
+
_format_check_result(result, verbose=verbose)
|
|
197
|
+
click.echo("")
|
|
198
|
+
|
|
199
|
+
# Display User Setup SECOND
|
|
200
|
+
click.echo(click.style("User Setup", bold=True))
|
|
201
|
+
# Prerequisites (always expanded)
|
|
202
|
+
for result in prerequisite_checks:
|
|
203
|
+
_format_check_result(result, verbose=verbose)
|
|
204
|
+
# User checks (condensable subgroup)
|
|
205
|
+
if verbose:
|
|
206
|
+
for subgroup_name, subgroup_check_names in USER_SUBGROUPS.items():
|
|
207
|
+
subgroup_checks = [r for r in user_checks if r.name in subgroup_check_names]
|
|
208
|
+
_format_subgroup(subgroup_name, subgroup_checks, verbose=True)
|
|
209
|
+
else:
|
|
210
|
+
for subgroup_name, subgroup_check_names in USER_SUBGROUPS.items():
|
|
211
|
+
subgroup_checks = [r for r in user_checks if r.name in subgroup_check_names]
|
|
212
|
+
_format_subgroup(subgroup_name, subgroup_checks, verbose=False)
|
|
213
|
+
click.echo("")
|
|
214
|
+
|
|
215
|
+
# Display any uncategorized checks (defensive - catches missing categorization)
|
|
216
|
+
other_checks = [r for r in results if r.name not in displayed_names]
|
|
217
|
+
if other_checks:
|
|
218
|
+
click.echo(click.style("Other Checks", bold=True))
|
|
219
|
+
for result in other_checks:
|
|
220
|
+
_format_check_result(result, verbose=verbose)
|
|
221
|
+
click.echo("")
|
|
222
|
+
|
|
223
|
+
# Collect and display consolidated remediations for failing checks
|
|
224
|
+
remediations = {r.remediation for r in results if r.remediation and not r.passed}
|
|
225
|
+
if remediations:
|
|
226
|
+
click.echo(click.style("Remediation", bold=True))
|
|
227
|
+
for remediation in sorted(remediations):
|
|
228
|
+
click.echo(f" {remediation}")
|
|
229
|
+
click.echo("")
|
|
230
|
+
|
|
231
|
+
# Calculate summary - exclude dogfooder checks from total if not showing them
|
|
232
|
+
checks_for_summary = [r for r in results if r.name not in EARLY_DOGFOODER_CHECK_NAMES]
|
|
233
|
+
if dogfooder:
|
|
234
|
+
checks_for_summary = results
|
|
235
|
+
|
|
236
|
+
passed = sum(1 for r in checks_for_summary if r.passed)
|
|
237
|
+
total = len(checks_for_summary)
|
|
238
|
+
failed = total - passed
|
|
239
|
+
|
|
240
|
+
if failed == 0:
|
|
241
|
+
click.echo(click.style("✨ All checks passed!", fg="green", bold=True))
|
|
242
|
+
else:
|
|
243
|
+
click.echo(click.style(f"⚠️ {failed} check(s) failed", fg="yellow", bold=True))
|
erk/cli/commands/down.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from erk.cli.commands.navigation_helpers import (
|
|
4
|
+
activate_root_repo,
|
|
5
|
+
activate_worktree,
|
|
6
|
+
check_clean_working_tree,
|
|
7
|
+
check_pending_extraction_marker,
|
|
8
|
+
render_activation_script,
|
|
9
|
+
resolve_down_navigation,
|
|
10
|
+
unallocate_worktree_and_branch,
|
|
11
|
+
verify_pr_closed_or_merged,
|
|
12
|
+
)
|
|
13
|
+
from erk.cli.core import discover_repo_context
|
|
14
|
+
from erk.cli.ensure import Ensure
|
|
15
|
+
from erk.cli.graphite_command import GraphiteCommandWithHiddenOptions
|
|
16
|
+
from erk.cli.help_formatter import script_option
|
|
17
|
+
from erk.core.context import ErkContext
|
|
18
|
+
from erk.core.worktree_utils import compute_relative_path_in_worktree
|
|
19
|
+
from erk_shared.output.output import machine_output, user_output
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command("down", cls=GraphiteCommandWithHiddenOptions)
|
|
23
|
+
@script_option
|
|
24
|
+
@click.option(
|
|
25
|
+
"--delete-current",
|
|
26
|
+
is_flag=True,
|
|
27
|
+
help="Delete current branch and worktree after navigating down",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"-f",
|
|
31
|
+
"--force",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="Force deletion even if marker exists or PR is open (prompts)",
|
|
34
|
+
)
|
|
35
|
+
@click.pass_obj
|
|
36
|
+
def down_cmd(ctx: ErkContext, script: bool, delete_current: bool, force: bool) -> None:
|
|
37
|
+
"""Move to parent branch in worktree stack.
|
|
38
|
+
|
|
39
|
+
With shell integration (recommended):
|
|
40
|
+
erk down
|
|
41
|
+
|
|
42
|
+
The shell wrapper function automatically activates the worktree.
|
|
43
|
+
Run 'erk init --shell' to set up shell integration.
|
|
44
|
+
|
|
45
|
+
Without shell integration:
|
|
46
|
+
source <(erk down --script)
|
|
47
|
+
|
|
48
|
+
This will cd to the parent branch's worktree (or root repo if parent is trunk),
|
|
49
|
+
create/activate .venv, and load .env variables.
|
|
50
|
+
Requires Graphite to be enabled: 'erk config set use_graphite true'
|
|
51
|
+
"""
|
|
52
|
+
# Validate preconditions upfront (LBYL)
|
|
53
|
+
Ensure.gh_authenticated(ctx)
|
|
54
|
+
|
|
55
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
56
|
+
trunk_branch = ctx.trunk_branch
|
|
57
|
+
|
|
58
|
+
# Get current branch
|
|
59
|
+
current_branch = Ensure.not_none(
|
|
60
|
+
ctx.git.get_current_branch(ctx.cwd), "Not currently on a branch (detached HEAD)"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Store current worktree path for deletion (before navigation)
|
|
64
|
+
# Find the worktree for the current branch
|
|
65
|
+
current_worktree_path = None
|
|
66
|
+
if delete_current:
|
|
67
|
+
current_worktree_path = Ensure.not_none(
|
|
68
|
+
ctx.git.find_worktree_for_branch(repo.root, current_branch),
|
|
69
|
+
f"Cannot find worktree for current branch '{current_branch}'.",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Safety checks before navigation (if --delete-current flag is set)
|
|
73
|
+
if delete_current and current_worktree_path is not None:
|
|
74
|
+
check_clean_working_tree(ctx)
|
|
75
|
+
verify_pr_closed_or_merged(ctx, repo.root, current_branch, force)
|
|
76
|
+
# Check for pending extraction marker
|
|
77
|
+
check_pending_extraction_marker(current_worktree_path, force)
|
|
78
|
+
|
|
79
|
+
# Get all worktrees for checking if target has a worktree
|
|
80
|
+
worktrees = ctx.git.list_worktrees(repo.root)
|
|
81
|
+
|
|
82
|
+
# Resolve navigation to get target branch or 'root' (may auto-create worktree)
|
|
83
|
+
target_name, was_created = resolve_down_navigation(
|
|
84
|
+
ctx, repo, current_branch, worktrees, trunk_branch
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Show creation message if worktree was just created
|
|
88
|
+
if was_created and not script:
|
|
89
|
+
user_output(
|
|
90
|
+
click.style("✓", fg="green")
|
|
91
|
+
+ f" Created worktree for {click.style(target_name, fg='yellow')} and moved to it"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Check if target_name refers to 'root' which means root repo
|
|
95
|
+
if target_name == "root":
|
|
96
|
+
if delete_current and current_worktree_path is not None:
|
|
97
|
+
# Handle activation inline so we can do cleanup before exiting
|
|
98
|
+
root_path = repo.root
|
|
99
|
+
if script:
|
|
100
|
+
script_content = render_activation_script(
|
|
101
|
+
worktree_path=root_path,
|
|
102
|
+
target_subpath=compute_relative_path_in_worktree(worktrees, ctx.cwd),
|
|
103
|
+
post_cd_commands=None,
|
|
104
|
+
final_message='echo "Went to root repo: $(pwd)"',
|
|
105
|
+
comment="work activate-script (root repo)",
|
|
106
|
+
)
|
|
107
|
+
result = ctx.script_writer.write_activation_script(
|
|
108
|
+
script_content,
|
|
109
|
+
command_name="down",
|
|
110
|
+
comment="activate root",
|
|
111
|
+
)
|
|
112
|
+
machine_output(str(result.path), nl=False)
|
|
113
|
+
else:
|
|
114
|
+
user_output(f"Went to root repo: {root_path}")
|
|
115
|
+
|
|
116
|
+
# Perform cleanup (no context regeneration needed - we haven't changed dirs)
|
|
117
|
+
unallocate_worktree_and_branch(ctx, repo, current_branch, current_worktree_path)
|
|
118
|
+
|
|
119
|
+
# Exit after cleanup
|
|
120
|
+
raise SystemExit(0)
|
|
121
|
+
else:
|
|
122
|
+
# No cleanup needed, use standard activation
|
|
123
|
+
activate_root_repo(ctx, repo, script, "down", post_cd_commands=None)
|
|
124
|
+
|
|
125
|
+
# Resolve target branch to actual worktree path
|
|
126
|
+
target_wt_path = Ensure.not_none(
|
|
127
|
+
ctx.git.find_worktree_for_branch(repo.root, target_name),
|
|
128
|
+
f"Branch '{target_name}' has no worktree. This should not happen.",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if delete_current and current_worktree_path is not None:
|
|
132
|
+
# Handle activation inline so we can do cleanup before exiting
|
|
133
|
+
Ensure.path_exists(ctx, target_wt_path, f"Worktree not found: {target_wt_path}")
|
|
134
|
+
|
|
135
|
+
if script:
|
|
136
|
+
activation_script = render_activation_script(
|
|
137
|
+
worktree_path=target_wt_path,
|
|
138
|
+
target_subpath=compute_relative_path_in_worktree(worktrees, ctx.cwd),
|
|
139
|
+
post_cd_commands=None,
|
|
140
|
+
final_message='echo "Activated worktree: $(pwd)"',
|
|
141
|
+
comment="work activate-script",
|
|
142
|
+
)
|
|
143
|
+
result = ctx.script_writer.write_activation_script(
|
|
144
|
+
activation_script,
|
|
145
|
+
command_name="down",
|
|
146
|
+
comment=f"activate {target_wt_path.name}",
|
|
147
|
+
)
|
|
148
|
+
machine_output(str(result.path), nl=False)
|
|
149
|
+
else:
|
|
150
|
+
user_output(
|
|
151
|
+
"Shell integration not detected. "
|
|
152
|
+
"Run 'erk init --shell' to set up automatic activation."
|
|
153
|
+
)
|
|
154
|
+
user_output("\nOr use: source <(erk down --script)")
|
|
155
|
+
|
|
156
|
+
# Perform cleanup (no context regeneration needed - we haven't actually changed directories)
|
|
157
|
+
unallocate_worktree_and_branch(ctx, repo, current_branch, current_worktree_path)
|
|
158
|
+
|
|
159
|
+
# Exit after cleanup
|
|
160
|
+
raise SystemExit(0)
|
|
161
|
+
else:
|
|
162
|
+
# No cleanup needed, use standard activation
|
|
163
|
+
activate_worktree(
|
|
164
|
+
ctx=ctx,
|
|
165
|
+
repo=repo,
|
|
166
|
+
target_path=target_wt_path,
|
|
167
|
+
script=script,
|
|
168
|
+
command_name="down",
|
|
169
|
+
preserve_relative_path=True,
|
|
170
|
+
post_cd_commands=None,
|
|
171
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Static exec group for erk scripts."""
|