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,89 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from erk.core.context import ErkContext
|
|
4
|
+
from erk_shared.output.output import machine_output
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group("completion")
|
|
8
|
+
def completion_group() -> None:
|
|
9
|
+
"""Generate shell completion scripts."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@completion_group.command("bash")
|
|
13
|
+
@click.pass_obj
|
|
14
|
+
def completion_bash(ctx: ErkContext) -> None:
|
|
15
|
+
"""Generate bash completion script.
|
|
16
|
+
|
|
17
|
+
\b
|
|
18
|
+
For automatic setup of both completion and auto-activation:
|
|
19
|
+
erk init --shell
|
|
20
|
+
|
|
21
|
+
\b
|
|
22
|
+
To load completions in your current shell session:
|
|
23
|
+
source <(erk completion bash)
|
|
24
|
+
|
|
25
|
+
\b
|
|
26
|
+
To load completions permanently, add to your ~/.bashrc:
|
|
27
|
+
echo 'source <(erk completion bash)' >> ~/.bashrc
|
|
28
|
+
|
|
29
|
+
\b
|
|
30
|
+
Alternatively, you can save the completion script to bash_completion.d:
|
|
31
|
+
erk completion bash > /usr/local/etc/bash_completion.d/erk
|
|
32
|
+
|
|
33
|
+
\b
|
|
34
|
+
You will need to start a new shell for this setup to take effect.
|
|
35
|
+
"""
|
|
36
|
+
script = ctx.completion.generate_bash()
|
|
37
|
+
machine_output(script, nl=False)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@completion_group.command("zsh")
|
|
41
|
+
@click.pass_obj
|
|
42
|
+
def completion_zsh(ctx: ErkContext) -> None:
|
|
43
|
+
"""Generate zsh completion script.
|
|
44
|
+
|
|
45
|
+
\b
|
|
46
|
+
For automatic setup of both completion and auto-activation:
|
|
47
|
+
erk init --shell
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
To load completions in your current shell session:
|
|
51
|
+
source <(erk completion zsh)
|
|
52
|
+
|
|
53
|
+
\b
|
|
54
|
+
To load completions permanently, add to your ~/.zshrc:
|
|
55
|
+
echo 'source <(erk completion zsh)' >> ~/.zshrc
|
|
56
|
+
|
|
57
|
+
\b
|
|
58
|
+
Note: Make sure compinit is called in your ~/.zshrc after loading completions.
|
|
59
|
+
|
|
60
|
+
\b
|
|
61
|
+
You will need to start a new shell for this setup to take effect.
|
|
62
|
+
"""
|
|
63
|
+
script = ctx.completion.generate_zsh()
|
|
64
|
+
machine_output(script, nl=False)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@completion_group.command("fish")
|
|
68
|
+
@click.pass_obj
|
|
69
|
+
def completion_fish(ctx: ErkContext) -> None:
|
|
70
|
+
"""Generate fish completion script.
|
|
71
|
+
|
|
72
|
+
\b
|
|
73
|
+
For automatic setup of both completion and auto-activation:
|
|
74
|
+
erk init --shell
|
|
75
|
+
|
|
76
|
+
\b
|
|
77
|
+
To load completions in your current shell session:
|
|
78
|
+
erk completion fish | source
|
|
79
|
+
|
|
80
|
+
\b
|
|
81
|
+
To load completions permanently:
|
|
82
|
+
mkdir -p ~/.config/fish/completions && \\
|
|
83
|
+
erk completion fish > ~/.config/fish/completions/erk.fish
|
|
84
|
+
|
|
85
|
+
\b
|
|
86
|
+
You will need to start a new shell for this setup to take effect.
|
|
87
|
+
"""
|
|
88
|
+
script = ctx.completion.generate_fish()
|
|
89
|
+
machine_output(script, nl=False)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Shell completion functions for CLI commands.
|
|
2
|
+
|
|
3
|
+
Separated from navigation_helpers to avoid circular imports.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import Generator
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from erk.cli.core import discover_repo_context
|
|
16
|
+
from erk.core.repo_discovery import ensure_erk_metadata_dir
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from erk.core.context import ErkContext
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@contextmanager
|
|
25
|
+
def shell_completion_context(ctx: click.Context) -> Generator[ErkContext]:
|
|
26
|
+
"""Context manager for shell completion that provides ErkContext with error handling.
|
|
27
|
+
|
|
28
|
+
Combines context extraction with graceful error handling for shell completion.
|
|
29
|
+
Suppresses all exceptions for graceful degradation.
|
|
30
|
+
|
|
31
|
+
Why this is needed:
|
|
32
|
+
- Shell completion runs in the user's interactive shell session
|
|
33
|
+
- Any uncaught exception would break the shell experience with a Python traceback
|
|
34
|
+
- Click's shell completion protocol expects functions to return empty lists on error
|
|
35
|
+
- This allows tab-completion to fail gracefully without disrupting the user
|
|
36
|
+
|
|
37
|
+
Why we create ErkContext if ctx.obj is None:
|
|
38
|
+
- Click's shell completion runs with resilient_parsing=True
|
|
39
|
+
- This mode skips command callbacks, so the cli() callback that creates ctx.obj never runs
|
|
40
|
+
- We must create a fresh ErkContext to provide completion data
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
with shell_completion_context(ctx) as erk_ctx:
|
|
44
|
+
# ... completion logic
|
|
45
|
+
return completion_candidates
|
|
46
|
+
return [] # Fallback if exception
|
|
47
|
+
|
|
48
|
+
Reference:
|
|
49
|
+
Click's shell completion protocol:
|
|
50
|
+
https://click.palletsprojects.com/en/stable/shell-completion/
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
root_ctx = ctx.find_root()
|
|
54
|
+
erk_ctx = root_ctx.obj
|
|
55
|
+
|
|
56
|
+
# Click's resilient_parsing mode skips callbacks, so ctx.obj may be None
|
|
57
|
+
if erk_ctx is None:
|
|
58
|
+
from erk.core.context import create_context
|
|
59
|
+
|
|
60
|
+
erk_ctx = create_context(dry_run=False)
|
|
61
|
+
|
|
62
|
+
yield erk_ctx
|
|
63
|
+
except Exception:
|
|
64
|
+
# Suppress exceptions for graceful degradation, but log for debugging
|
|
65
|
+
# Shell completion should never break the user's shell experience
|
|
66
|
+
logger.debug("Shell completion error", exc_info=True)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def complete_worktree_names(
|
|
70
|
+
ctx: click.Context, param: click.Parameter | None, incomplete: str
|
|
71
|
+
) -> list[str]:
|
|
72
|
+
"""Shell completion for worktree names. Includes 'root' for the repository root.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
ctx: Click context
|
|
76
|
+
param: Click parameter (unused, but required by Click's completion protocol)
|
|
77
|
+
incomplete: Partial input string to complete
|
|
78
|
+
"""
|
|
79
|
+
with shell_completion_context(ctx) as erk_ctx:
|
|
80
|
+
repo = discover_repo_context(erk_ctx, erk_ctx.cwd)
|
|
81
|
+
ensure_erk_metadata_dir(repo)
|
|
82
|
+
|
|
83
|
+
names = ["root"] if "root".startswith(incomplete) else []
|
|
84
|
+
|
|
85
|
+
# Get worktree names from git_ops instead of filesystem iteration
|
|
86
|
+
worktrees = erk_ctx.git.list_worktrees(repo.root)
|
|
87
|
+
for wt in worktrees:
|
|
88
|
+
if wt.is_root:
|
|
89
|
+
continue # Skip root worktree (already added as "root")
|
|
90
|
+
worktree_name = wt.path.name
|
|
91
|
+
if worktree_name.startswith(incomplete):
|
|
92
|
+
names.append(worktree_name)
|
|
93
|
+
|
|
94
|
+
return names
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def complete_branch_names(
|
|
99
|
+
ctx: click.Context, param: click.Parameter | None, incomplete: str
|
|
100
|
+
) -> list[str]:
|
|
101
|
+
"""Shell completion for branch names. Includes both local and remote branches.
|
|
102
|
+
|
|
103
|
+
Remote branch names have their remote prefix stripped
|
|
104
|
+
(e.g., 'origin/feature' becomes 'feature').
|
|
105
|
+
Duplicates are removed if a branch exists both locally and remotely.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
ctx: Click context
|
|
109
|
+
param: Click parameter (unused, but required by Click's completion protocol)
|
|
110
|
+
incomplete: Partial input string to complete
|
|
111
|
+
"""
|
|
112
|
+
with shell_completion_context(ctx) as erk_ctx:
|
|
113
|
+
repo = discover_repo_context(erk_ctx, erk_ctx.cwd)
|
|
114
|
+
ensure_erk_metadata_dir(repo)
|
|
115
|
+
|
|
116
|
+
# Collect all branch names in a set for deduplication
|
|
117
|
+
branch_names = set()
|
|
118
|
+
|
|
119
|
+
# Add local branches
|
|
120
|
+
local_branches = erk_ctx.git.list_local_branches(repo.root)
|
|
121
|
+
branch_names.update(local_branches)
|
|
122
|
+
|
|
123
|
+
# Add remote branches with prefix stripped
|
|
124
|
+
remote_branches = erk_ctx.git.list_remote_branches(repo.root)
|
|
125
|
+
for remote_branch in remote_branches:
|
|
126
|
+
# Strip remote prefix (e.g., 'origin/feature' -> 'feature')
|
|
127
|
+
if "/" in remote_branch:
|
|
128
|
+
_, branch_name = remote_branch.split("/", 1)
|
|
129
|
+
branch_names.add(branch_name)
|
|
130
|
+
else:
|
|
131
|
+
# Fallback: if no slash, use as-is
|
|
132
|
+
branch_names.add(remote_branch)
|
|
133
|
+
|
|
134
|
+
# Filter by incomplete prefix and return sorted list
|
|
135
|
+
matching_branches = [name for name in branch_names if name.startswith(incomplete)]
|
|
136
|
+
return sorted(matching_branches)
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def complete_plan_files(
|
|
141
|
+
ctx: click.Context, param: click.Parameter | None, incomplete: str
|
|
142
|
+
) -> list[str]:
|
|
143
|
+
"""Shell completion for plan files (markdown files in current directory).
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
ctx: Click context
|
|
147
|
+
param: Click parameter (unused, but required by Click's completion protocol)
|
|
148
|
+
incomplete: Partial input string to complete
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of completion candidates (filenames matching incomplete text)
|
|
152
|
+
"""
|
|
153
|
+
with shell_completion_context(ctx) as erk_ctx:
|
|
154
|
+
# Get current working directory from erk context
|
|
155
|
+
cwd = erk_ctx.cwd
|
|
156
|
+
|
|
157
|
+
# Find all .md files in current directory
|
|
158
|
+
candidates = []
|
|
159
|
+
for md_file in cwd.glob("*.md"):
|
|
160
|
+
# Filter by incomplete prefix if provided
|
|
161
|
+
if md_file.name.startswith(incomplete):
|
|
162
|
+
candidates.append(md_file.name)
|
|
163
|
+
|
|
164
|
+
return sorted(candidates)
|
|
165
|
+
return []
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from collections.abc import MutableMapping
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
from functools import cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import tomlkit
|
|
10
|
+
|
|
11
|
+
from erk.cli.commands.slot.common import DEFAULT_POOL_SIZE
|
|
12
|
+
from erk.cli.config import LoadedConfig
|
|
13
|
+
from erk.cli.core import discover_repo_context
|
|
14
|
+
from erk.cli.ensure import Ensure
|
|
15
|
+
from erk.core.context import ErkContext, write_trunk_to_pyproject
|
|
16
|
+
from erk_shared.output.output import machine_output, user_output
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cache
|
|
20
|
+
def get_global_config_keys() -> dict[str, str]:
|
|
21
|
+
"""Get user-exposed global config keys with descriptions.
|
|
22
|
+
|
|
23
|
+
Order determines display order in 'erk config list'.
|
|
24
|
+
shell_setup_complete is internal and not exposed.
|
|
25
|
+
"""
|
|
26
|
+
return {
|
|
27
|
+
"erk_root": "Root directory for erk data (~/.erk by default)",
|
|
28
|
+
"use_graphite": "Enable Graphite integration for stack management",
|
|
29
|
+
"show_pr_info": "Show PR status in branch listings",
|
|
30
|
+
"github_planning": "Enable GitHub issues integration for planning",
|
|
31
|
+
"fix_conflicts_require_dangerous_flag": "Require --dangerous flag for fix-conflicts",
|
|
32
|
+
"show_hidden_commands": "Show deprecated/hidden commands in help output",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_env_value(cfg: LoadedConfig, parts: list[str], key: str) -> None:
|
|
37
|
+
"""Handle env.* configuration keys.
|
|
38
|
+
|
|
39
|
+
Prints the value or exits with error if key not found.
|
|
40
|
+
"""
|
|
41
|
+
Ensure.invariant(len(parts) == 2, f"Invalid key: {key}")
|
|
42
|
+
Ensure.invariant(parts[1] in cfg.env, f"Key not found: {key}")
|
|
43
|
+
|
|
44
|
+
machine_output(cfg.env[parts[1]])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_post_create_value(cfg: LoadedConfig, parts: list[str], key: str) -> None:
|
|
48
|
+
"""Handle post_create.* configuration keys.
|
|
49
|
+
|
|
50
|
+
Prints the value or exits with error if key not found.
|
|
51
|
+
"""
|
|
52
|
+
Ensure.invariant(len(parts) == 2, f"Invalid key: {key}")
|
|
53
|
+
|
|
54
|
+
# Handle shell subkey
|
|
55
|
+
if parts[1] == "shell":
|
|
56
|
+
Ensure.truthy(cfg.post_create_shell, f"Key not found: {key}")
|
|
57
|
+
machine_output(cfg.post_create_shell)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Handle commands subkey
|
|
61
|
+
if parts[1] == "commands":
|
|
62
|
+
for cmd in cfg.post_create_commands:
|
|
63
|
+
machine_output(cmd)
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
# Unknown subkey
|
|
67
|
+
Ensure.invariant(False, f"Key not found: {key}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _write_pool_max_slots(repo_root: Path, max_slots: int) -> None:
|
|
71
|
+
"""Write pool.max_slots to .erk/config.toml.
|
|
72
|
+
|
|
73
|
+
Creates or updates the [pool] section with max_slots setting.
|
|
74
|
+
Preserves existing formatting and comments using tomlkit.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
repo_root: Path to the repository root directory
|
|
78
|
+
max_slots: Maximum number of pool slots to configure
|
|
79
|
+
"""
|
|
80
|
+
config_dir = repo_root / ".erk"
|
|
81
|
+
config_path = config_dir / "config.toml"
|
|
82
|
+
|
|
83
|
+
# Ensure .erk directory exists
|
|
84
|
+
if not config_dir.exists():
|
|
85
|
+
config_dir.mkdir(parents=True)
|
|
86
|
+
|
|
87
|
+
# Load existing file or create new document
|
|
88
|
+
if config_path.exists():
|
|
89
|
+
with config_path.open("r", encoding="utf-8") as f:
|
|
90
|
+
doc = tomlkit.load(f)
|
|
91
|
+
else:
|
|
92
|
+
doc = tomlkit.document()
|
|
93
|
+
|
|
94
|
+
# Ensure [pool] section exists
|
|
95
|
+
if "pool" not in doc:
|
|
96
|
+
assert isinstance(doc, MutableMapping), f"Expected MutableMapping, got {type(doc)}"
|
|
97
|
+
cast(dict[str, Any], doc)["pool"] = tomlkit.table()
|
|
98
|
+
|
|
99
|
+
# Set max_slots value
|
|
100
|
+
pool_section = doc["pool"]
|
|
101
|
+
assert isinstance(pool_section, MutableMapping), type(pool_section)
|
|
102
|
+
cast(dict[str, Any], pool_section)["max_slots"] = max_slots
|
|
103
|
+
|
|
104
|
+
# Write back to file
|
|
105
|
+
with config_path.open("w", encoding="utf-8") as f:
|
|
106
|
+
tomlkit.dump(doc, f)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@click.group("config")
|
|
110
|
+
def config_group() -> None:
|
|
111
|
+
"""Manage erk configuration."""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@config_group.command("keys")
|
|
115
|
+
def config_keys() -> None:
|
|
116
|
+
"""List all available configuration keys with descriptions."""
|
|
117
|
+
formatter = click.HelpFormatter()
|
|
118
|
+
|
|
119
|
+
# Global config section
|
|
120
|
+
user_output(click.style("Global configuration keys:", bold=True))
|
|
121
|
+
rows = list(get_global_config_keys().items())
|
|
122
|
+
formatter.write_dl(rows)
|
|
123
|
+
user_output(formatter.getvalue().rstrip())
|
|
124
|
+
|
|
125
|
+
user_output("")
|
|
126
|
+
|
|
127
|
+
# Repository config section
|
|
128
|
+
user_output(click.style("Repository configuration keys:", bold=True))
|
|
129
|
+
formatter = click.HelpFormatter()
|
|
130
|
+
repo_keys = [
|
|
131
|
+
("trunk-branch", "The main/master branch name for the repository"),
|
|
132
|
+
("pool.max_slots", "Maximum number of pool slots for worktree pool"),
|
|
133
|
+
("env.<name>", "Environment variables to set in worktrees"),
|
|
134
|
+
("post_create.shell", "Shell to use for post-create commands"),
|
|
135
|
+
("post_create.commands", "Commands to run after creating a worktree"),
|
|
136
|
+
]
|
|
137
|
+
formatter.write_dl(repo_keys)
|
|
138
|
+
user_output(formatter.getvalue().rstrip())
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _format_config_value(value: object) -> str:
|
|
142
|
+
"""Format a config value for display."""
|
|
143
|
+
if isinstance(value, bool):
|
|
144
|
+
return str(value).lower()
|
|
145
|
+
return str(value)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@config_group.command("list")
|
|
149
|
+
@click.pass_obj
|
|
150
|
+
def config_list(ctx: ErkContext) -> None:
|
|
151
|
+
"""Print a list of configuration keys and values."""
|
|
152
|
+
# Display global config
|
|
153
|
+
user_output(click.style("Global configuration:", bold=True))
|
|
154
|
+
if ctx.global_config:
|
|
155
|
+
for key in get_global_config_keys():
|
|
156
|
+
value = getattr(ctx.global_config, key)
|
|
157
|
+
user_output(f" {key}={_format_config_value(value)}")
|
|
158
|
+
else:
|
|
159
|
+
user_output(" (not configured - run 'erk init' to create)")
|
|
160
|
+
|
|
161
|
+
# Display local config
|
|
162
|
+
user_output(click.style("\nRepository configuration:", bold=True))
|
|
163
|
+
from erk.core.repo_discovery import NoRepoSentinel
|
|
164
|
+
|
|
165
|
+
if isinstance(ctx.repo, NoRepoSentinel):
|
|
166
|
+
user_output(" (not in a git repository)")
|
|
167
|
+
else:
|
|
168
|
+
trunk_branch = ctx.trunk_branch
|
|
169
|
+
cfg = ctx.local_config
|
|
170
|
+
if trunk_branch:
|
|
171
|
+
user_output(f" trunk-branch={trunk_branch}")
|
|
172
|
+
if cfg.pool_size is not None:
|
|
173
|
+
user_output(f" pool.max_slots={cfg.pool_size}")
|
|
174
|
+
else:
|
|
175
|
+
user_output(f" pool.max_slots={DEFAULT_POOL_SIZE} (default)")
|
|
176
|
+
if cfg.env:
|
|
177
|
+
for key, value in cfg.env.items():
|
|
178
|
+
user_output(f" env.{key}={value}")
|
|
179
|
+
if cfg.post_create_shell:
|
|
180
|
+
user_output(f" post_create.shell={cfg.post_create_shell}")
|
|
181
|
+
if cfg.post_create_commands:
|
|
182
|
+
user_output(f" post_create.commands={cfg.post_create_commands}")
|
|
183
|
+
|
|
184
|
+
has_no_custom_config = (
|
|
185
|
+
not trunk_branch
|
|
186
|
+
and cfg.pool_size is None
|
|
187
|
+
and not cfg.env
|
|
188
|
+
and not cfg.post_create_shell
|
|
189
|
+
and not cfg.post_create_commands
|
|
190
|
+
)
|
|
191
|
+
if has_no_custom_config:
|
|
192
|
+
user_output(" (no custom configuration - run 'erk init' to create)")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@config_group.command("get")
|
|
196
|
+
@click.argument("key", metavar="KEY")
|
|
197
|
+
@click.pass_obj
|
|
198
|
+
def config_get(ctx: ErkContext, key: str) -> None:
|
|
199
|
+
"""Print the value of a given configuration key."""
|
|
200
|
+
parts = key.split(".")
|
|
201
|
+
|
|
202
|
+
# Handle global config keys
|
|
203
|
+
if parts[0] in get_global_config_keys():
|
|
204
|
+
global_config = Ensure.not_none(
|
|
205
|
+
ctx.global_config, f"Global config not found at {ctx.erk_installation.config_path()}"
|
|
206
|
+
)
|
|
207
|
+
value = getattr(global_config, parts[0])
|
|
208
|
+
machine_output(_format_config_value(value))
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Handle repo config keys
|
|
212
|
+
from erk.core.repo_discovery import NoRepoSentinel
|
|
213
|
+
|
|
214
|
+
if isinstance(ctx.repo, NoRepoSentinel):
|
|
215
|
+
user_output("Not in a git repository")
|
|
216
|
+
raise SystemExit(1)
|
|
217
|
+
|
|
218
|
+
if parts[0] == "trunk-branch":
|
|
219
|
+
trunk_branch = ctx.trunk_branch
|
|
220
|
+
if trunk_branch:
|
|
221
|
+
machine_output(trunk_branch)
|
|
222
|
+
else:
|
|
223
|
+
user_output("not configured (will auto-detect)")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
cfg = ctx.local_config
|
|
227
|
+
|
|
228
|
+
if parts[0] == "env":
|
|
229
|
+
_get_env_value(cfg, parts, key)
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
if parts[0] == "post_create":
|
|
233
|
+
_get_post_create_value(cfg, parts, key)
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
if parts[0] == "pool" and len(parts) == 2 and parts[1] == "max_slots":
|
|
237
|
+
if cfg.pool_size is not None:
|
|
238
|
+
machine_output(str(cfg.pool_size))
|
|
239
|
+
else:
|
|
240
|
+
machine_output(f"{DEFAULT_POOL_SIZE} (default)")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
user_output(f"Invalid key: {key}")
|
|
244
|
+
raise SystemExit(1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _parse_config_value(key: str, value: str, current_type: type) -> object:
|
|
248
|
+
"""Parse a string value to the appropriate type for a config key."""
|
|
249
|
+
if current_type is bool:
|
|
250
|
+
if value.lower() not in ("true", "false"):
|
|
251
|
+
user_output(f"Invalid boolean value: {value}")
|
|
252
|
+
raise SystemExit(1)
|
|
253
|
+
return value.lower() == "true"
|
|
254
|
+
if current_type is Path or key == "erk_root":
|
|
255
|
+
return Path(value).expanduser().resolve()
|
|
256
|
+
return value
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@config_group.command("set")
|
|
260
|
+
@click.argument("key", metavar="KEY")
|
|
261
|
+
@click.argument("value", metavar="VALUE")
|
|
262
|
+
@click.pass_obj
|
|
263
|
+
def config_set(ctx: ErkContext, key: str, value: str) -> None:
|
|
264
|
+
"""Update configuration with a value for the given key."""
|
|
265
|
+
# Parse key into parts
|
|
266
|
+
parts = key.split(".")
|
|
267
|
+
|
|
268
|
+
# Handle global config keys
|
|
269
|
+
if parts[0] in get_global_config_keys():
|
|
270
|
+
config_path = ctx.erk_installation.config_path()
|
|
271
|
+
global_config = Ensure.not_none(
|
|
272
|
+
ctx.global_config,
|
|
273
|
+
f"Global config not found at {config_path}. Run 'erk init' to create it.",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Get current value's type and parse new value
|
|
277
|
+
current_value = getattr(global_config, parts[0])
|
|
278
|
+
parsed_value = _parse_config_value(parts[0], value, type(current_value))
|
|
279
|
+
|
|
280
|
+
# Create new config with updated value using dataclasses.replace
|
|
281
|
+
new_config = replace(global_config, **{parts[0]: parsed_value})
|
|
282
|
+
|
|
283
|
+
ctx.erk_installation.save_config(new_config)
|
|
284
|
+
user_output(f"Set {key}={value}")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
# Handle repo config keys
|
|
288
|
+
if parts[0] == "trunk-branch":
|
|
289
|
+
# discover_repo_context checks for git repository and raises FileNotFoundError
|
|
290
|
+
repo = discover_repo_context(ctx, Path.cwd())
|
|
291
|
+
|
|
292
|
+
# Validate that the branch exists before writing
|
|
293
|
+
result = subprocess.run(
|
|
294
|
+
["git", "rev-parse", "--verify", value],
|
|
295
|
+
cwd=repo.root,
|
|
296
|
+
capture_output=True,
|
|
297
|
+
text=True,
|
|
298
|
+
check=False,
|
|
299
|
+
)
|
|
300
|
+
Ensure.invariant(
|
|
301
|
+
result.returncode == 0,
|
|
302
|
+
f"Branch '{value}' doesn't exist in repository.\n"
|
|
303
|
+
f"Create the branch first before configuring it as trunk.",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Write configuration
|
|
307
|
+
write_trunk_to_pyproject(repo.root, value)
|
|
308
|
+
user_output(f"Set trunk-branch={value}")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# Handle pool.max_slots
|
|
312
|
+
if parts[0] == "pool" and len(parts) == 2 and parts[1] == "max_slots":
|
|
313
|
+
repo = discover_repo_context(ctx, Path.cwd())
|
|
314
|
+
|
|
315
|
+
# Validate value is a positive integer
|
|
316
|
+
if not value.isdigit() or int(value) < 1:
|
|
317
|
+
user_output(f"Invalid value: {value}. pool.max_slots must be a positive integer.")
|
|
318
|
+
raise SystemExit(1)
|
|
319
|
+
|
|
320
|
+
pool_size = int(value)
|
|
321
|
+
_write_pool_max_slots(repo.root, pool_size)
|
|
322
|
+
user_output(f"Set pool.max_slots={pool_size}")
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Other repo config keys not implemented yet
|
|
326
|
+
user_output(f"Invalid key: {key}")
|
|
327
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent documentation command group."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Agent documentation command group."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.docs.sync import sync_command
|
|
6
|
+
from erk.cli.commands.docs.validate import validate_command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group(name="docs")
|
|
10
|
+
def docs_group() -> None:
|
|
11
|
+
"""Manage and validate agent documentation."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Register commands
|
|
15
|
+
docs_group.add_command(sync_command)
|
|
16
|
+
docs_group.add_command(validate_command)
|