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,16 @@
|
|
|
1
|
+
"""Project management commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.project.init_cmd import init_project
|
|
6
|
+
from erk.cli.help_formatter import ErkCommandGroup
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group("project", cls=ErkCommandGroup, grouped=False)
|
|
10
|
+
def project_group() -> None:
|
|
11
|
+
"""Manage project or projects (within a monorepo)."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Register subcommands
|
|
16
|
+
project_group.add_command(init_project)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Project init command - initialize a project in the current directory."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.core import discover_repo_context
|
|
6
|
+
from erk.core.context import ErkContext
|
|
7
|
+
from erk_shared.output.output import user_output
|
|
8
|
+
|
|
9
|
+
# Template for project.toml
|
|
10
|
+
PROJECT_TOML_TEMPLATE = """\
|
|
11
|
+
# Project configuration for erk
|
|
12
|
+
# This file identifies this directory as an erk project within a monorepo.
|
|
13
|
+
|
|
14
|
+
# Optional: custom project name (defaults to directory name)
|
|
15
|
+
# name = "{project_name}"
|
|
16
|
+
|
|
17
|
+
[env]
|
|
18
|
+
# Project-specific environment variables (merged with repo-level config)
|
|
19
|
+
# These variables are available in .env files created for worktrees
|
|
20
|
+
# Example:
|
|
21
|
+
# DAGSTER_HOME = "{{project_root}}"
|
|
22
|
+
|
|
23
|
+
[post_create]
|
|
24
|
+
# Commands to run after worktree creation, FROM the project directory
|
|
25
|
+
# These run AFTER repo-level post_create commands
|
|
26
|
+
# shell = "bash"
|
|
27
|
+
# commands = [
|
|
28
|
+
# "source .venv/bin/activate",
|
|
29
|
+
# ]
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.command("init")
|
|
34
|
+
@click.pass_obj
|
|
35
|
+
def init_project(ctx: ErkContext) -> None:
|
|
36
|
+
"""Initialize a project in the current directory.
|
|
37
|
+
|
|
38
|
+
Creates a .erk/project.toml file that identifies this directory as
|
|
39
|
+
a project within a monorepo. When worktrees are created from this
|
|
40
|
+
project context, erk will:
|
|
41
|
+
|
|
42
|
+
- Record the project path in worktrees.toml
|
|
43
|
+
- Navigate to the project subdirectory on `erk wt co`
|
|
44
|
+
- Merge project-level config with repo-level config
|
|
45
|
+
- Run project-specific post_create commands
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
cd /code/internal/python_modules/my-project
|
|
49
|
+
erk project init
|
|
50
|
+
"""
|
|
51
|
+
# Validate we're in a git repo
|
|
52
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
53
|
+
|
|
54
|
+
# Don't allow init at repo root (check before project.toml to give clearer error)
|
|
55
|
+
if ctx.cwd.resolve() == repo.root.resolve():
|
|
56
|
+
user_output(
|
|
57
|
+
click.style("Error: ", fg="red") + "Cannot initialize project at repository root.\n"
|
|
58
|
+
"Projects are subdirectories within a repo. "
|
|
59
|
+
"Use `erk init` for repository-level configuration."
|
|
60
|
+
)
|
|
61
|
+
raise SystemExit(1)
|
|
62
|
+
|
|
63
|
+
# Check if project.toml already exists
|
|
64
|
+
project_toml_path = ctx.cwd / ".erk" / "project.toml"
|
|
65
|
+
if ctx.git.path_exists(project_toml_path):
|
|
66
|
+
user_output(
|
|
67
|
+
click.style("Error: ", fg="red") + f"Project already initialized: {project_toml_path}"
|
|
68
|
+
)
|
|
69
|
+
raise SystemExit(1)
|
|
70
|
+
|
|
71
|
+
# Create .erk directory and project.toml
|
|
72
|
+
erk_dir = ctx.cwd / ".erk"
|
|
73
|
+
erk_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
project_name = ctx.cwd.name
|
|
76
|
+
content = PROJECT_TOML_TEMPLATE.format(project_name=project_name)
|
|
77
|
+
project_toml_path.write_text(content, encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
# Calculate path from repo root for display
|
|
80
|
+
path_from_repo = ctx.cwd.relative_to(repo.root)
|
|
81
|
+
|
|
82
|
+
user_output(
|
|
83
|
+
click.style("✓ ", fg="green")
|
|
84
|
+
+ f"Initialized project: {click.style(project_name, fg='cyan', bold=True)}"
|
|
85
|
+
)
|
|
86
|
+
user_output(f" Location: {path_from_repo}")
|
|
87
|
+
user_output(f" Config: {project_toml_path}")
|
|
88
|
+
user_output("")
|
|
89
|
+
user_output("Next steps:")
|
|
90
|
+
user_output(f" 1. Edit {project_toml_path} to configure project settings")
|
|
91
|
+
user_output(" 2. Create a worktree from this project: erk wt create <name>")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Run management commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.run.list_cmd import list_runs
|
|
6
|
+
from erk.cli.commands.run.logs_cmd import logs_run
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group("run")
|
|
10
|
+
def run_group() -> None:
|
|
11
|
+
"""View GitHub Actions workflow runs for plan implementations."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Register subcommands
|
|
16
|
+
run_group.add_command(list_runs)
|
|
17
|
+
run_group.add_command(logs_run)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""List workflow runs command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from erk.cli.commands.plan.list_cmd import format_pr_cell, select_display_pr
|
|
8
|
+
from erk.cli.commands.run.shared import extract_issue_number
|
|
9
|
+
from erk.cli.constants import DISPATCH_WORKFLOW_NAME
|
|
10
|
+
from erk.cli.core import discover_repo_context
|
|
11
|
+
from erk.cli.ensure import Ensure
|
|
12
|
+
from erk.core.context import ErkContext
|
|
13
|
+
from erk.core.display_utils import (
|
|
14
|
+
format_submission_time,
|
|
15
|
+
format_workflow_outcome,
|
|
16
|
+
format_workflow_run_id,
|
|
17
|
+
)
|
|
18
|
+
from erk_shared.github.emoji import format_checks_cell
|
|
19
|
+
from erk_shared.github.parsing import github_repo_location_from_url
|
|
20
|
+
from erk_shared.github.types import GitHubRepoId
|
|
21
|
+
from erk_shared.output.output import user_output
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _list_runs(ctx: ErkContext, show_all: bool = False) -> None:
|
|
25
|
+
"""List workflow runs in a run-centric table view."""
|
|
26
|
+
# Validate preconditions upfront (LBYL)
|
|
27
|
+
Ensure.gh_authenticated(ctx)
|
|
28
|
+
|
|
29
|
+
# Discover repository context
|
|
30
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
31
|
+
|
|
32
|
+
# 1. Fetch workflow runs from dispatch workflow
|
|
33
|
+
runs = ctx.github.list_workflow_runs(repo.root, DISPATCH_WORKFLOW_NAME)
|
|
34
|
+
|
|
35
|
+
# Handle empty state
|
|
36
|
+
if not runs:
|
|
37
|
+
user_output("No workflow runs found")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Filter out runs without plans unless --show-legacy flag is set
|
|
41
|
+
if not show_all:
|
|
42
|
+
runs = [run for run in runs if extract_issue_number(run.display_title) is not None]
|
|
43
|
+
if not runs:
|
|
44
|
+
user_output("No runs with plans found. Use --show-legacy to see all runs.")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# 2. Extract issue numbers from display_title (format: "123:abc456")
|
|
48
|
+
issue_numbers: list[int] = []
|
|
49
|
+
for run in runs:
|
|
50
|
+
issue_num = extract_issue_number(run.display_title)
|
|
51
|
+
if issue_num is not None:
|
|
52
|
+
issue_numbers.append(issue_num)
|
|
53
|
+
|
|
54
|
+
# 3. Fetch issues for titles (using issues interface)
|
|
55
|
+
issues = ctx.issues.list_issues(repo.root, labels=["erk-plan"])
|
|
56
|
+
issue_map = {issue.number: issue for issue in issues}
|
|
57
|
+
|
|
58
|
+
# Second filtering pass - remove runs where we can't display title
|
|
59
|
+
if not show_all:
|
|
60
|
+
filtered_runs = []
|
|
61
|
+
for run in runs:
|
|
62
|
+
issue_num = extract_issue_number(run.display_title)
|
|
63
|
+
if issue_num is None:
|
|
64
|
+
continue # Already filtered, but defensive check
|
|
65
|
+
|
|
66
|
+
# Filter if issue not found
|
|
67
|
+
if issue_num not in issue_map:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Filter if title is empty
|
|
71
|
+
issue = issue_map[issue_num]
|
|
72
|
+
if not issue.title or not issue.title.strip():
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
filtered_runs.append(run)
|
|
76
|
+
|
|
77
|
+
runs = filtered_runs
|
|
78
|
+
|
|
79
|
+
# Show message if ALL runs filtered
|
|
80
|
+
if not runs:
|
|
81
|
+
user_output("No runs with plans found. Use --show-legacy to see all runs.")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Extract location from first issue URL (needed for API calls and links)
|
|
85
|
+
location = None
|
|
86
|
+
if issues:
|
|
87
|
+
location = github_repo_location_from_url(repo.root, issues[0].url)
|
|
88
|
+
|
|
89
|
+
# 4. Batch fetch PRs linked to issues
|
|
90
|
+
pr_linkages: dict[int, list] = {}
|
|
91
|
+
if issue_numbers and location is not None:
|
|
92
|
+
pr_linkages = ctx.github.get_prs_linked_to_issues(location, issue_numbers)
|
|
93
|
+
|
|
94
|
+
# Determine use_graphite for URL selection
|
|
95
|
+
use_graphite = ctx.global_config.use_graphite if ctx.global_config else False
|
|
96
|
+
|
|
97
|
+
# 5. Build table
|
|
98
|
+
table = Table(show_header=True, header_style="bold")
|
|
99
|
+
table.add_column("run-id", style="cyan", no_wrap=True)
|
|
100
|
+
table.add_column("status", no_wrap=True, width=14)
|
|
101
|
+
table.add_column("submitted", no_wrap=True, width=11)
|
|
102
|
+
table.add_column("plan", no_wrap=True)
|
|
103
|
+
table.add_column("title", no_wrap=True)
|
|
104
|
+
table.add_column("pr", no_wrap=True)
|
|
105
|
+
table.add_column("chks", no_wrap=True)
|
|
106
|
+
|
|
107
|
+
for run in runs:
|
|
108
|
+
issue_num = extract_issue_number(run.display_title)
|
|
109
|
+
|
|
110
|
+
# Format run-id with link
|
|
111
|
+
workflow_url = None
|
|
112
|
+
if location is not None:
|
|
113
|
+
workflow_url = f"https://github.com/{location.repo_id.owner}/{location.repo_id.repo}/actions/runs/{run.run_id}"
|
|
114
|
+
run_id_cell = format_workflow_run_id(run, workflow_url)
|
|
115
|
+
|
|
116
|
+
# Format status
|
|
117
|
+
status_cell = format_workflow_outcome(run)
|
|
118
|
+
|
|
119
|
+
# Format submission time
|
|
120
|
+
submitted_cell = format_submission_time(run.created_at)
|
|
121
|
+
|
|
122
|
+
# Handle legacy runs where we can't parse the issue number
|
|
123
|
+
# Show "X" to indicate "can't parse" vs "-" for "no data"
|
|
124
|
+
if issue_num is None:
|
|
125
|
+
# Legacy format - can't extract issue linkage
|
|
126
|
+
plan_cell = "[dim]X[/dim]"
|
|
127
|
+
title_cell = "[dim]X[/dim]"
|
|
128
|
+
pr_cell = "[dim]X[/dim]"
|
|
129
|
+
checks_cell = "[dim]X[/dim]"
|
|
130
|
+
else:
|
|
131
|
+
# New format - have issue number, try to get data
|
|
132
|
+
issue_url = None
|
|
133
|
+
if location is not None:
|
|
134
|
+
issue_url = f"https://github.com/{location.repo_id.owner}/{location.repo_id.repo}/issues/{issue_num}"
|
|
135
|
+
# Make plan number clickable
|
|
136
|
+
if issue_url:
|
|
137
|
+
plan_cell = f"[link={issue_url}][cyan]#{issue_num}[/cyan][/link]"
|
|
138
|
+
else:
|
|
139
|
+
plan_cell = f"[cyan]#{issue_num}[/cyan]"
|
|
140
|
+
|
|
141
|
+
# Get title from issue map
|
|
142
|
+
if issue_num in issue_map:
|
|
143
|
+
issue = issue_map[issue_num]
|
|
144
|
+
|
|
145
|
+
title = issue.title
|
|
146
|
+
# Truncate to 50 characters
|
|
147
|
+
if len(title) > 50:
|
|
148
|
+
title = title[:47] + "..."
|
|
149
|
+
title_cell = title
|
|
150
|
+
else:
|
|
151
|
+
title_cell = "[dim]-[/dim]"
|
|
152
|
+
|
|
153
|
+
# Format PR column
|
|
154
|
+
pr_cell = "-"
|
|
155
|
+
checks_cell = "-"
|
|
156
|
+
if issue_num in pr_linkages:
|
|
157
|
+
prs = pr_linkages[issue_num]
|
|
158
|
+
selected_pr = select_display_pr(prs)
|
|
159
|
+
if selected_pr is not None:
|
|
160
|
+
graphite_url = ctx.graphite.get_graphite_url(
|
|
161
|
+
GitHubRepoId(selected_pr.owner, selected_pr.repo), selected_pr.number
|
|
162
|
+
)
|
|
163
|
+
pr_cell = format_pr_cell(
|
|
164
|
+
selected_pr, use_graphite=use_graphite, graphite_url=graphite_url
|
|
165
|
+
)
|
|
166
|
+
checks_cell = format_checks_cell(selected_pr)
|
|
167
|
+
|
|
168
|
+
table.add_row(
|
|
169
|
+
run_id_cell,
|
|
170
|
+
status_cell,
|
|
171
|
+
submitted_cell,
|
|
172
|
+
plan_cell,
|
|
173
|
+
title_cell,
|
|
174
|
+
pr_cell,
|
|
175
|
+
checks_cell,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Output table to stderr (consistent with user_output convention)
|
|
179
|
+
console = Console(stderr=True, width=200, force_terminal=True)
|
|
180
|
+
console.print(table)
|
|
181
|
+
console.print() # Add blank line after table
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@click.command("list")
|
|
185
|
+
@click.option("--show-legacy", is_flag=True, help="Show all runs including legacy runs.")
|
|
186
|
+
@click.pass_obj
|
|
187
|
+
def list_runs(ctx: ErkContext, show_legacy: bool) -> None:
|
|
188
|
+
"""List GitHub Actions workflow runs for plan implementations."""
|
|
189
|
+
_list_runs(ctx, show_legacy)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""View workflow run logs command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.core import discover_repo_context
|
|
6
|
+
from erk.cli.ensure import Ensure
|
|
7
|
+
from erk.core.context import ErkContext
|
|
8
|
+
from erk_shared.output.output import user_output
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command("logs")
|
|
12
|
+
@click.argument("run_id", required=False)
|
|
13
|
+
@click.pass_obj
|
|
14
|
+
def logs_run(ctx: ErkContext, run_id: str | None) -> None:
|
|
15
|
+
"""View logs for a workflow run.
|
|
16
|
+
|
|
17
|
+
If RUN_ID is not provided, shows logs for the most recent run
|
|
18
|
+
on the current branch.
|
|
19
|
+
"""
|
|
20
|
+
# Validate preconditions upfront (LBYL)
|
|
21
|
+
Ensure.gh_authenticated(ctx)
|
|
22
|
+
|
|
23
|
+
# Discover repository context
|
|
24
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
25
|
+
|
|
26
|
+
if run_id is None:
|
|
27
|
+
# Auto-detect: find most recent run for current branch
|
|
28
|
+
current_branch = Ensure.not_none(
|
|
29
|
+
ctx.git.get_current_branch(ctx.cwd), "Could not determine current branch"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
runs = ctx.github.list_workflow_runs(repo.root, "implement-plan.yml", limit=50)
|
|
33
|
+
branch_runs = [r for r in runs if r.branch == current_branch]
|
|
34
|
+
|
|
35
|
+
if not branch_runs:
|
|
36
|
+
user_output(
|
|
37
|
+
f"No workflow runs found for branch: {click.style(current_branch, fg='yellow')}"
|
|
38
|
+
)
|
|
39
|
+
raise SystemExit(1)
|
|
40
|
+
|
|
41
|
+
# Most recent is first (list_workflow_runs returns newest first)
|
|
42
|
+
run_id = branch_runs[0].run_id
|
|
43
|
+
user_output(
|
|
44
|
+
f"Showing logs for run {click.style(run_id, fg='cyan')} "
|
|
45
|
+
f"on branch {click.style(current_branch, fg='yellow')}\n"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
log_output = ctx.github.get_run_logs(repo.root, run_id)
|
|
50
|
+
# Direct output - logs go to stdout for piping
|
|
51
|
+
click.echo(log_output)
|
|
52
|
+
except RuntimeError as e:
|
|
53
|
+
click.echo(click.style("Error: ", fg="red") + str(e), err=True)
|
|
54
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shared utilities for run commands."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extract_issue_number(display_title: str | None) -> int | None:
|
|
5
|
+
"""Extract issue number from display_title format '123:abc456'.
|
|
6
|
+
|
|
7
|
+
Handles:
|
|
8
|
+
- New format: "123:abc456" → 123
|
|
9
|
+
- Old format: "Issue title [abc123]" → None (no colon at start)
|
|
10
|
+
- None or empty → None
|
|
11
|
+
"""
|
|
12
|
+
if not display_title or ":" not in display_title:
|
|
13
|
+
return None
|
|
14
|
+
parts = display_title.split(":", 1)
|
|
15
|
+
# Validate that the first part is a number
|
|
16
|
+
first_part = parts[0].strip()
|
|
17
|
+
if not first_part.isdigit():
|
|
18
|
+
return None
|
|
19
|
+
return int(first_part)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from erk.cli.shell_integration.handler import (
|
|
4
|
+
PASSTHROUGH_MARKER,
|
|
5
|
+
ShellIntegrationResult,
|
|
6
|
+
handle_shell_request,
|
|
7
|
+
)
|
|
8
|
+
from erk_shared.output.output import machine_output
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(
|
|
12
|
+
"__shell",
|
|
13
|
+
hidden=True,
|
|
14
|
+
add_help_option=False,
|
|
15
|
+
context_settings={"ignore_unknown_options": True, "allow_interspersed_args": False},
|
|
16
|
+
)
|
|
17
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
18
|
+
def hidden_shell_cmd(args: tuple[str, ...]) -> None:
|
|
19
|
+
"""Unified entry point for shell integration wrappers."""
|
|
20
|
+
result: ShellIntegrationResult = handle_shell_request(args)
|
|
21
|
+
|
|
22
|
+
if result.passthrough:
|
|
23
|
+
machine_output(PASSTHROUGH_MARKER)
|
|
24
|
+
raise SystemExit(result.exit_code)
|
|
25
|
+
|
|
26
|
+
if result.script:
|
|
27
|
+
machine_output(result.script, nl=False)
|
|
28
|
+
|
|
29
|
+
raise SystemExit(result.exit_code)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Slot infrastructure management commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.alias import register_with_aliases
|
|
6
|
+
from erk.cli.commands.slot.check_cmd import slot_check
|
|
7
|
+
from erk.cli.commands.slot.init_pool_cmd import slot_init_pool
|
|
8
|
+
from erk.cli.commands.slot.list_cmd import slot_list
|
|
9
|
+
from erk.cli.commands.slot.repair_cmd import slot_repair
|
|
10
|
+
from erk.cli.help_formatter import ErkCommandGroup
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group("slot", cls=ErkCommandGroup, grouped=False)
|
|
14
|
+
def slot_group() -> None:
|
|
15
|
+
"""Manage worktree pool slots (infrastructure only)."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Register subcommands
|
|
20
|
+
slot_group.add_command(slot_check)
|
|
21
|
+
slot_group.add_command(slot_init_pool)
|
|
22
|
+
slot_group.add_command(slot_repair)
|
|
23
|
+
register_with_aliases(slot_group, slot_list)
|