erk 0.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erk/__init__.py +12 -0
- erk/__main__.py +6 -0
- erk/agent_docs/__init__.py +5 -0
- erk/agent_docs/models.py +123 -0
- erk/agent_docs/operations.py +666 -0
- erk/artifacts/__init__.py +5 -0
- erk/artifacts/artifact_health.py +623 -0
- erk/artifacts/detection.py +16 -0
- erk/artifacts/discovery.py +343 -0
- erk/artifacts/models.py +63 -0
- erk/artifacts/staleness.py +56 -0
- erk/artifacts/state.py +100 -0
- erk/artifacts/sync.py +624 -0
- erk/cli/__init__.py +0 -0
- erk/cli/activation.py +132 -0
- erk/cli/alias.py +53 -0
- erk/cli/cli.py +221 -0
- erk/cli/commands/__init__.py +0 -0
- erk/cli/commands/admin.py +153 -0
- erk/cli/commands/artifact/__init__.py +1 -0
- erk/cli/commands/artifact/check.py +260 -0
- erk/cli/commands/artifact/group.py +31 -0
- erk/cli/commands/artifact/list_cmd.py +89 -0
- erk/cli/commands/artifact/show.py +62 -0
- erk/cli/commands/artifact/sync_cmd.py +39 -0
- erk/cli/commands/branch/__init__.py +26 -0
- erk/cli/commands/branch/assign_cmd.py +152 -0
- erk/cli/commands/branch/checkout_cmd.py +357 -0
- erk/cli/commands/branch/create_cmd.py +161 -0
- erk/cli/commands/branch/list_cmd.py +82 -0
- erk/cli/commands/branch/unassign_cmd.py +197 -0
- erk/cli/commands/cc/__init__.py +15 -0
- erk/cli/commands/cc/jsonl_cmd.py +20 -0
- erk/cli/commands/cc/session/AGENTS.md +30 -0
- erk/cli/commands/cc/session/CLAUDE.md +1 -0
- erk/cli/commands/cc/session/__init__.py +15 -0
- erk/cli/commands/cc/session/list_cmd.py +167 -0
- erk/cli/commands/cc/session/show_cmd.py +175 -0
- erk/cli/commands/completion.py +89 -0
- erk/cli/commands/completions.py +165 -0
- erk/cli/commands/config.py +327 -0
- erk/cli/commands/docs/__init__.py +1 -0
- erk/cli/commands/docs/group.py +16 -0
- erk/cli/commands/docs/sync.py +121 -0
- erk/cli/commands/docs/validate.py +102 -0
- erk/cli/commands/doctor.py +243 -0
- erk/cli/commands/down.py +171 -0
- erk/cli/commands/exec/__init__.py +1 -0
- erk/cli/commands/exec/group.py +164 -0
- erk/cli/commands/exec/scripts/AGENTS.md +79 -0
- erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
- erk/cli/commands/exec/scripts/__init__.py +5 -0
- erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
- erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
- erk/cli/commands/exec/scripts/check_impl.py +152 -0
- erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
- erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
- erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
- erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
- erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
- erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
- erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
- erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
- erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
- erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
- erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
- erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
- erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
- erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
- erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
- erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
- erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
- erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
- erk/cli/commands/exec/scripts/impl_init.py +158 -0
- erk/cli/commands/exec/scripts/impl_signal.py +375 -0
- erk/cli/commands/exec/scripts/impl_verify.py +49 -0
- erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
- erk/cli/commands/exec/scripts/list_sessions.py +296 -0
- erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
- erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
- erk/cli/commands/exec/scripts/marker.py +163 -0
- erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
- erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
- erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
- erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
- erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
- erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
- erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
- erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
- erk/cli/commands/exec/scripts/quick_submit.py +32 -0
- erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
- erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
- erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
- erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
- erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
- erk/cli/commands/exec/scripts/slot_objective.py +102 -0
- erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
- erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
- erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
- erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
- erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
- erk/cli/commands/implement.py +695 -0
- erk/cli/commands/implement_shared.py +649 -0
- erk/cli/commands/info/__init__.py +14 -0
- erk/cli/commands/info/release_notes_cmd.py +128 -0
- erk/cli/commands/init.py +801 -0
- erk/cli/commands/land_cmd.py +690 -0
- erk/cli/commands/log_cmd.py +137 -0
- erk/cli/commands/md/__init__.py +5 -0
- erk/cli/commands/md/check.py +118 -0
- erk/cli/commands/md/group.py +14 -0
- erk/cli/commands/navigation_helpers.py +430 -0
- erk/cli/commands/objective/__init__.py +16 -0
- erk/cli/commands/objective/list_cmd.py +47 -0
- erk/cli/commands/objective_helpers.py +132 -0
- erk/cli/commands/plan/__init__.py +32 -0
- erk/cli/commands/plan/check_cmd.py +174 -0
- erk/cli/commands/plan/close_cmd.py +69 -0
- erk/cli/commands/plan/create_cmd.py +120 -0
- erk/cli/commands/plan/docs/__init__.py +18 -0
- erk/cli/commands/plan/docs/extract_cmd.py +53 -0
- erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
- erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
- erk/cli/commands/plan/extraction/__init__.py +16 -0
- erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
- erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
- erk/cli/commands/plan/get.py +71 -0
- erk/cli/commands/plan/list_cmd.py +754 -0
- erk/cli/commands/plan/log_cmd.py +440 -0
- erk/cli/commands/plan/start_cmd.py +459 -0
- erk/cli/commands/planner/__init__.py +40 -0
- erk/cli/commands/planner/configure_cmd.py +73 -0
- erk/cli/commands/planner/connect_cmd.py +96 -0
- erk/cli/commands/planner/create_cmd.py +148 -0
- erk/cli/commands/planner/list_cmd.py +51 -0
- erk/cli/commands/planner/register_cmd.py +105 -0
- erk/cli/commands/planner/set_default_cmd.py +23 -0
- erk/cli/commands/planner/unregister_cmd.py +43 -0
- erk/cli/commands/pr/__init__.py +23 -0
- erk/cli/commands/pr/check_cmd.py +112 -0
- erk/cli/commands/pr/checkout_cmd.py +165 -0
- erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
- erk/cli/commands/pr/parse_pr_reference.py +10 -0
- erk/cli/commands/pr/submit_cmd.py +360 -0
- erk/cli/commands/pr/sync_cmd.py +181 -0
- erk/cli/commands/prepare_cwd_recovery.py +60 -0
- erk/cli/commands/project/__init__.py +16 -0
- erk/cli/commands/project/init_cmd.py +91 -0
- erk/cli/commands/run/__init__.py +17 -0
- erk/cli/commands/run/list_cmd.py +189 -0
- erk/cli/commands/run/logs_cmd.py +54 -0
- erk/cli/commands/run/shared.py +19 -0
- erk/cli/commands/shell_integration.py +29 -0
- erk/cli/commands/slot/__init__.py +23 -0
- erk/cli/commands/slot/check_cmd.py +277 -0
- erk/cli/commands/slot/common.py +314 -0
- erk/cli/commands/slot/init_pool_cmd.py +157 -0
- erk/cli/commands/slot/list_cmd.py +228 -0
- erk/cli/commands/slot/repair_cmd.py +190 -0
- erk/cli/commands/stack/__init__.py +23 -0
- erk/cli/commands/stack/consolidate_cmd.py +470 -0
- erk/cli/commands/stack/list_cmd.py +79 -0
- erk/cli/commands/stack/move_cmd.py +309 -0
- erk/cli/commands/stack/split_old/README.md +64 -0
- erk/cli/commands/stack/split_old/__init__.py +5 -0
- erk/cli/commands/stack/split_old/command.py +233 -0
- erk/cli/commands/stack/split_old/display.py +116 -0
- erk/cli/commands/stack/split_old/plan.py +216 -0
- erk/cli/commands/status.py +58 -0
- erk/cli/commands/submit.py +768 -0
- erk/cli/commands/up.py +154 -0
- erk/cli/commands/upgrade.py +82 -0
- erk/cli/commands/wt/__init__.py +29 -0
- erk/cli/commands/wt/checkout_cmd.py +110 -0
- erk/cli/commands/wt/create_cmd.py +998 -0
- erk/cli/commands/wt/current_cmd.py +35 -0
- erk/cli/commands/wt/delete_cmd.py +573 -0
- erk/cli/commands/wt/list_cmd.py +332 -0
- erk/cli/commands/wt/rename_cmd.py +66 -0
- erk/cli/config.py +242 -0
- erk/cli/constants.py +29 -0
- erk/cli/core.py +65 -0
- erk/cli/debug.py +9 -0
- erk/cli/ensure-conversion-tasks.md +288 -0
- erk/cli/ensure.py +628 -0
- erk/cli/github_parsing.py +96 -0
- erk/cli/graphite.py +81 -0
- erk/cli/graphite_command.py +80 -0
- erk/cli/help_formatter.py +345 -0
- erk/cli/output.py +361 -0
- erk/cli/presets/dagster.toml +12 -0
- erk/cli/presets/generic.toml +12 -0
- erk/cli/prompt_hooks_templates/README.md +68 -0
- erk/cli/script_output.py +32 -0
- erk/cli/shell_integration/bash_wrapper.sh +32 -0
- erk/cli/shell_integration/fish_wrapper.fish +39 -0
- erk/cli/shell_integration/handler.py +338 -0
- erk/cli/shell_integration/zsh_wrapper.sh +32 -0
- erk/cli/shell_utils.py +171 -0
- erk/cli/subprocess_utils.py +92 -0
- erk/cli/uvx_detection.py +59 -0
- erk/core/__init__.py +0 -0
- erk/core/claude_executor.py +511 -0
- erk/core/claude_settings.py +317 -0
- erk/core/command_log.py +406 -0
- erk/core/commit_message_generator.py +234 -0
- erk/core/completion.py +10 -0
- erk/core/consolidation_utils.py +177 -0
- erk/core/context.py +570 -0
- erk/core/display/__init__.py +4 -0
- erk/core/display/abc.py +24 -0
- erk/core/display/real.py +30 -0
- erk/core/display_utils.py +526 -0
- erk/core/file_utils.py +87 -0
- erk/core/health_checks.py +1315 -0
- erk/core/health_checks_dogfooder/__init__.py +85 -0
- erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
- erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
- erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
- erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
- erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
- erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
- erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
- erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
- erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
- erk/core/implementation_queue/__init__.py +1 -0
- erk/core/implementation_queue/github/__init__.py +8 -0
- erk/core/implementation_queue/github/abc.py +7 -0
- erk/core/implementation_queue/github/noop.py +38 -0
- erk/core/implementation_queue/github/printing.py +43 -0
- erk/core/implementation_queue/github/real.py +119 -0
- erk/core/init_utils.py +227 -0
- erk/core/output_filter.py +338 -0
- erk/core/plan_store/__init__.py +6 -0
- erk/core/planner/__init__.py +1 -0
- erk/core/planner/registry_abc.py +8 -0
- erk/core/planner/registry_fake.py +129 -0
- erk/core/planner/registry_real.py +195 -0
- erk/core/planner/types.py +7 -0
- erk/core/pr_utils.py +30 -0
- erk/core/release_notes.py +263 -0
- erk/core/repo_discovery.py +126 -0
- erk/core/script_writer.py +41 -0
- erk/core/services/__init__.py +1 -0
- erk/core/services/plan_list_service.py +94 -0
- erk/core/shell.py +51 -0
- erk/core/user_feedback.py +11 -0
- erk/core/version_check.py +55 -0
- erk/core/workflow_display.py +75 -0
- erk/core/worktree_pool.py +190 -0
- erk/core/worktree_utils.py +300 -0
- erk/data/CHANGELOG.md +438 -0
- erk/data/__init__.py +1 -0
- erk/data/claude/agents/devrun.md +180 -0
- erk/data/claude/commands/erk/__init__.py +0 -0
- erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
- erk/data/claude/commands/erk/fix-conflicts.md +25 -0
- erk/data/claude/commands/erk/git-pr-push.md +345 -0
- erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
- erk/data/claude/commands/erk/land.md +193 -0
- erk/data/claude/commands/erk/objective-create.md +370 -0
- erk/data/claude/commands/erk/objective-list.md +34 -0
- erk/data/claude/commands/erk/objective-next-plan.md +220 -0
- erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
- erk/data/claude/commands/erk/plan-implement.md +202 -0
- erk/data/claude/commands/erk/plan-save.md +45 -0
- erk/data/claude/commands/erk/plan-submit.md +39 -0
- erk/data/claude/commands/erk/pr-address.md +367 -0
- erk/data/claude/commands/erk/pr-submit.md +58 -0
- erk/data/claude/skills/dignified-python/SKILL.md +48 -0
- erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
- erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
- erk/data/claude/skills/dignified-python/subprocess.md +99 -0
- erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
- erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
- erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
- erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
- erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
- erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
- erk/data/claude/skills/learned-docs/SKILL.md +362 -0
- erk/data/github/actions/setup-claude-erk/action.yml +11 -0
- erk/data/github/prompts/dignified-python-review.md +125 -0
- erk/data/github/workflows/dignified-python-review.yml +61 -0
- erk/data/github/workflows/erk-impl.yml +251 -0
- erk/hooks/__init__.py +1 -0
- erk/hooks/decorators.py +319 -0
- erk/status/__init__.py +8 -0
- erk/status/collectors/__init__.py +9 -0
- erk/status/collectors/base.py +52 -0
- erk/status/collectors/git.py +76 -0
- erk/status/collectors/github.py +81 -0
- erk/status/collectors/graphite.py +80 -0
- erk/status/collectors/impl.py +145 -0
- erk/status/models/__init__.py +4 -0
- erk/status/models/status_data.py +404 -0
- erk/status/orchestrator.py +169 -0
- erk/status/renderers/__init__.py +5 -0
- erk/status/renderers/simple.py +322 -0
- erk/tui/AGENTS.md +193 -0
- erk/tui/CLAUDE.md +1 -0
- erk/tui/__init__.py +1 -0
- erk/tui/app.py +1404 -0
- erk/tui/commands/__init__.py +1 -0
- erk/tui/commands/executor.py +66 -0
- erk/tui/commands/provider.py +165 -0
- erk/tui/commands/real_executor.py +63 -0
- erk/tui/commands/registry.py +121 -0
- erk/tui/commands/types.py +36 -0
- erk/tui/data/__init__.py +1 -0
- erk/tui/data/provider.py +492 -0
- erk/tui/data/types.py +104 -0
- erk/tui/filtering/__init__.py +1 -0
- erk/tui/filtering/logic.py +43 -0
- erk/tui/filtering/types.py +55 -0
- erk/tui/jsonl_viewer/__init__.py +1 -0
- erk/tui/jsonl_viewer/app.py +61 -0
- erk/tui/jsonl_viewer/models.py +208 -0
- erk/tui/jsonl_viewer/widgets.py +204 -0
- erk/tui/sorting/__init__.py +6 -0
- erk/tui/sorting/logic.py +55 -0
- erk/tui/sorting/types.py +68 -0
- erk/tui/styles/dash.tcss +95 -0
- erk/tui/widgets/__init__.py +1 -0
- erk/tui/widgets/command_output.py +112 -0
- erk/tui/widgets/plan_table.py +276 -0
- erk/tui/widgets/status_bar.py +116 -0
- erk-0.4.5.dist-info/METADATA +376 -0
- erk-0.4.5.dist-info/RECORD +331 -0
- erk-0.4.5.dist-info/WHEEL +4 -0
- erk-0.4.5.dist-info/entry_points.txt +2 -0
- erk-0.4.5.dist-info/licenses/LICENSE.md +3 -0
erk/cli/output.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Output utilities for CLI commands with clear intent.
|
|
2
|
+
|
|
3
|
+
For user_output, machine_output, format_duration - import from erk_shared.output.
|
|
4
|
+
This module provides format_implement_summary and stream_command_with_feedback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
|
|
16
|
+
from erk.core.claude_executor import (
|
|
17
|
+
ClaudeExecutor,
|
|
18
|
+
CommandResult,
|
|
19
|
+
ErrorEvent,
|
|
20
|
+
IssueNumberEvent,
|
|
21
|
+
NoOutputEvent,
|
|
22
|
+
NoTurnsEvent,
|
|
23
|
+
PrNumberEvent,
|
|
24
|
+
ProcessErrorEvent,
|
|
25
|
+
PrTitleEvent,
|
|
26
|
+
PrUrlEvent,
|
|
27
|
+
SpinnerUpdateEvent,
|
|
28
|
+
TextEvent,
|
|
29
|
+
ToolEvent,
|
|
30
|
+
)
|
|
31
|
+
from erk_shared.output.output import format_duration
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def format_implement_summary(results: list[CommandResult], total_duration: float) -> Panel:
|
|
35
|
+
"""Format final summary box with status, PR link, timing, errors.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
results: List of CommandResult from executed commands
|
|
39
|
+
total_duration: Total execution time in seconds
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Rich Panel with formatted summary
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> results = [CommandResult(success=True, pr_url="https://...", ...)]
|
|
46
|
+
>>> panel = format_implement_summary(results, 123.45)
|
|
47
|
+
>>> console.print(panel)
|
|
48
|
+
"""
|
|
49
|
+
# Determine overall success
|
|
50
|
+
overall_success = all(r.success for r in results)
|
|
51
|
+
|
|
52
|
+
# Build summary lines
|
|
53
|
+
lines: list[Text] = []
|
|
54
|
+
|
|
55
|
+
# Status line
|
|
56
|
+
if overall_success:
|
|
57
|
+
lines.append(Text("✅ Status: Success", style="green"))
|
|
58
|
+
else:
|
|
59
|
+
lines.append(Text("❌ Status: Failed", style="red"))
|
|
60
|
+
|
|
61
|
+
# Duration
|
|
62
|
+
duration_str = format_duration(total_duration)
|
|
63
|
+
lines.append(Text(f"⏱ Duration: {duration_str}"))
|
|
64
|
+
|
|
65
|
+
# PR and issue metadata (if any)
|
|
66
|
+
pr_url: str | None = None
|
|
67
|
+
pr_number: int | None = None
|
|
68
|
+
pr_title: str | None = None
|
|
69
|
+
issue_number: int | None = None
|
|
70
|
+
for result in results:
|
|
71
|
+
if result.pr_url:
|
|
72
|
+
pr_url = result.pr_url
|
|
73
|
+
pr_number = result.pr_number
|
|
74
|
+
pr_title = result.pr_title
|
|
75
|
+
issue_number = result.issue_number
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
if pr_url:
|
|
79
|
+
# Add blank line for spacing
|
|
80
|
+
lines.append(Text(""))
|
|
81
|
+
|
|
82
|
+
# Show PR number with URL
|
|
83
|
+
if pr_number:
|
|
84
|
+
lines.append(Text(f"🔗 PR: #{pr_number}", style="blue bold"))
|
|
85
|
+
else:
|
|
86
|
+
lines.append(Text("🔗 PR: Created", style="blue bold"))
|
|
87
|
+
|
|
88
|
+
# Show PR title
|
|
89
|
+
if pr_title:
|
|
90
|
+
lines.append(Text(f" {pr_title}", style="cyan"))
|
|
91
|
+
|
|
92
|
+
# Show PR URL
|
|
93
|
+
lines.append(Text(f" {pr_url}", style="dim"))
|
|
94
|
+
|
|
95
|
+
# Show linked issue (if any)
|
|
96
|
+
if issue_number:
|
|
97
|
+
lines.append(Text(""))
|
|
98
|
+
lines.append(
|
|
99
|
+
Text(f"📋 Linked Issue: #{issue_number} (will auto-close on merge)", style="yellow")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Error details (if failed)
|
|
103
|
+
if not overall_success:
|
|
104
|
+
for i, result in enumerate(results):
|
|
105
|
+
if not result.success:
|
|
106
|
+
if result.error_message:
|
|
107
|
+
lines.append(Text("")) # Blank line
|
|
108
|
+
lines.append(Text(f"Error in command {i + 1}:", style="red bold"))
|
|
109
|
+
lines.append(Text(result.error_message, style="red"))
|
|
110
|
+
|
|
111
|
+
# Combine lines
|
|
112
|
+
content = Text("\n").join(lines)
|
|
113
|
+
|
|
114
|
+
# Create panel
|
|
115
|
+
title = "Implementation Complete" if overall_success else "Implementation Failed"
|
|
116
|
+
return Panel(
|
|
117
|
+
content, title=title, border_style="green" if overall_success else "red", padding=(1, 2)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def stream_command_with_feedback(
|
|
122
|
+
executor: ClaudeExecutor,
|
|
123
|
+
command: str,
|
|
124
|
+
worktree_path: Path,
|
|
125
|
+
dangerous: bool,
|
|
126
|
+
model: str | None = None,
|
|
127
|
+
debug: bool = False,
|
|
128
|
+
) -> CommandResult:
|
|
129
|
+
"""Stream Claude command execution with live print-based feedback.
|
|
130
|
+
|
|
131
|
+
This function replaces spinner-based output with print-based feedback
|
|
132
|
+
that works correctly (Rich's console.status() suppresses console.print()).
|
|
133
|
+
|
|
134
|
+
Visual output format:
|
|
135
|
+
- Start: `--- /command ---` (bold)
|
|
136
|
+
- Text events: content as-is (normal)
|
|
137
|
+
- Tool events: ` > tool summary` (dim)
|
|
138
|
+
- Spinner updates: ` ... status` (dim, deduplicated)
|
|
139
|
+
- Error events: ` ! error message` (red)
|
|
140
|
+
- End (success): `--- Done (1m 23s) ---` (green)
|
|
141
|
+
- End (failure): `--- Failed (1m 23s) ---` (red)
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
executor: Claude CLI executor for command execution
|
|
145
|
+
command: The slash command to execute (e.g., "/gt:pr-submit")
|
|
146
|
+
worktree_path: Path to worktree directory to run command in
|
|
147
|
+
dangerous: Whether to skip permission prompts
|
|
148
|
+
model: Optional model name (haiku, sonnet, opus) to pass to Claude CLI
|
|
149
|
+
debug: Whether to show debug output for stream parsing
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
CommandResult with success status, PR URL, duration, and messages
|
|
153
|
+
"""
|
|
154
|
+
# Flush stderr to ensure previous user_output() messages are visible
|
|
155
|
+
# before stdout starts printing. This prevents buffering issues where
|
|
156
|
+
# stderr output appears after stdout in mixed output scenarios.
|
|
157
|
+
sys.stderr.flush()
|
|
158
|
+
|
|
159
|
+
# Print start marker (stderr so shell integration can capture stdout)
|
|
160
|
+
click.echo(click.style(f"--- {command} ---", bold=True), err=True)
|
|
161
|
+
|
|
162
|
+
start_time = time.time()
|
|
163
|
+
filtered_messages: list[str] = []
|
|
164
|
+
pr_url: str | None = None
|
|
165
|
+
pr_number: int | None = None
|
|
166
|
+
pr_title: str | None = None
|
|
167
|
+
issue_number: int | None = None
|
|
168
|
+
error_message: str | None = None
|
|
169
|
+
success = True
|
|
170
|
+
last_spinner_update: str | None = None
|
|
171
|
+
event_count = 0
|
|
172
|
+
|
|
173
|
+
# Stream events in real-time
|
|
174
|
+
event_stream = executor.execute_command_streaming(
|
|
175
|
+
command, worktree_path, dangerous, verbose=False, debug=debug, model=model
|
|
176
|
+
)
|
|
177
|
+
if debug:
|
|
178
|
+
click.echo(click.style("[DEBUG] Starting event stream...", fg="yellow"), err=True)
|
|
179
|
+
for event in event_stream:
|
|
180
|
+
event_count += 1
|
|
181
|
+
if debug:
|
|
182
|
+
click.echo(
|
|
183
|
+
click.style(f"[DEBUG] Event #{event_count}: {type(event).__name__}", fg="yellow"),
|
|
184
|
+
err=True,
|
|
185
|
+
)
|
|
186
|
+
match event:
|
|
187
|
+
case TextEvent(content=content):
|
|
188
|
+
click.echo(content, err=True)
|
|
189
|
+
filtered_messages.append(content)
|
|
190
|
+
case ToolEvent(summary=summary):
|
|
191
|
+
click.echo(click.style(f" > {summary}", dim=True), err=True)
|
|
192
|
+
filtered_messages.append(summary)
|
|
193
|
+
case SpinnerUpdateEvent(status=status):
|
|
194
|
+
# Deduplicate spinner updates - only print when status changes
|
|
195
|
+
if status != last_spinner_update:
|
|
196
|
+
click.echo(click.style(f" ... {status}", dim=True), err=True)
|
|
197
|
+
last_spinner_update = status
|
|
198
|
+
case PrUrlEvent(url=url):
|
|
199
|
+
pr_url = url
|
|
200
|
+
case PrNumberEvent(number=num):
|
|
201
|
+
pr_number = num # Already int, no conversion needed
|
|
202
|
+
case PrTitleEvent(title=title):
|
|
203
|
+
pr_title = title
|
|
204
|
+
case IssueNumberEvent(number=num):
|
|
205
|
+
issue_number = num # Already int, no conversion needed
|
|
206
|
+
case ErrorEvent(message=msg):
|
|
207
|
+
click.echo(click.style(f" ! {msg}", fg="red"), err=True)
|
|
208
|
+
error_message = msg
|
|
209
|
+
success = False
|
|
210
|
+
case NoOutputEvent(diagnostic=diag):
|
|
211
|
+
click.echo(click.style(f" ⚠️ {diag}", fg="yellow"), err=True)
|
|
212
|
+
error_message = diag
|
|
213
|
+
success = False
|
|
214
|
+
case NoTurnsEvent(diagnostic=diag):
|
|
215
|
+
click.echo(click.style(f" ⚠️ {diag}", fg="yellow"), err=True)
|
|
216
|
+
error_message = diag
|
|
217
|
+
success = False
|
|
218
|
+
case ProcessErrorEvent(message=msg):
|
|
219
|
+
click.echo(click.style(f" ❌ {msg}", fg="red"), err=True)
|
|
220
|
+
error_message = msg
|
|
221
|
+
success = False
|
|
222
|
+
|
|
223
|
+
if debug:
|
|
224
|
+
msg = f"[DEBUG] Event stream complete. Total events: {event_count}"
|
|
225
|
+
click.echo(click.style(msg, fg="yellow"), err=True)
|
|
226
|
+
|
|
227
|
+
duration = time.time() - start_time
|
|
228
|
+
duration_str = format_duration(duration)
|
|
229
|
+
|
|
230
|
+
# Print end marker (stderr so shell integration can capture stdout)
|
|
231
|
+
if success:
|
|
232
|
+
click.echo(click.style(f"--- Done ({duration_str}) ---", fg="green", bold=True), err=True)
|
|
233
|
+
else:
|
|
234
|
+
click.echo(click.style(f"--- Failed ({duration_str}) ---", fg="red", bold=True), err=True)
|
|
235
|
+
|
|
236
|
+
return CommandResult(
|
|
237
|
+
success=success,
|
|
238
|
+
pr_url=pr_url,
|
|
239
|
+
pr_number=pr_number,
|
|
240
|
+
pr_title=pr_title,
|
|
241
|
+
issue_number=issue_number,
|
|
242
|
+
duration_seconds=duration,
|
|
243
|
+
error_message=error_message,
|
|
244
|
+
filtered_messages=filtered_messages,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@dataclass(frozen=True)
|
|
249
|
+
class FixConflictsResult:
|
|
250
|
+
"""Result from fix-conflicts streaming execution."""
|
|
251
|
+
|
|
252
|
+
success: bool
|
|
253
|
+
error_message: str | None = None
|
|
254
|
+
requires_interactive: bool = False
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def stream_fix_conflicts(
|
|
258
|
+
executor: ClaudeExecutor,
|
|
259
|
+
worktree_path: Path,
|
|
260
|
+
) -> FixConflictsResult:
|
|
261
|
+
"""Stream fix-conflicts command via Claude executor with live feedback.
|
|
262
|
+
|
|
263
|
+
Handles the /erk:fix-conflicts command execution with:
|
|
264
|
+
- Live output streaming with visual feedback
|
|
265
|
+
- Semantic conflict detection (AskUserQuestion)
|
|
266
|
+
- Deduped spinner updates
|
|
267
|
+
- Rich console output with start/end markers
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
executor: Claude CLI executor
|
|
271
|
+
worktree_path: Path to run the conflict resolution in
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
FixConflictsResult with success status and error details
|
|
275
|
+
"""
|
|
276
|
+
error_message: str | None = None
|
|
277
|
+
success = True
|
|
278
|
+
has_work_events = False
|
|
279
|
+
last_spinner: str | None = None
|
|
280
|
+
start_time = time.time()
|
|
281
|
+
|
|
282
|
+
# Print start marker with bold styling
|
|
283
|
+
click.echo(click.style("--- /erk:fix-conflicts ---", bold=True))
|
|
284
|
+
click.echo("")
|
|
285
|
+
|
|
286
|
+
for event in executor.execute_command_streaming(
|
|
287
|
+
command="/erk:fix-conflicts",
|
|
288
|
+
worktree_path=worktree_path,
|
|
289
|
+
dangerous=True, # Conflict resolution modifies git state
|
|
290
|
+
):
|
|
291
|
+
match event:
|
|
292
|
+
case TextEvent(content=content):
|
|
293
|
+
has_work_events = True
|
|
294
|
+
click.echo(content)
|
|
295
|
+
case ToolEvent(summary=summary):
|
|
296
|
+
has_work_events = True
|
|
297
|
+
# Check for user input prompts (semantic conflict requiring decision)
|
|
298
|
+
if "AskUserQuestion" in summary:
|
|
299
|
+
click.echo("")
|
|
300
|
+
click.echo(
|
|
301
|
+
click.style(
|
|
302
|
+
"⚠️ Semantic conflict detected - requires interactive resolution",
|
|
303
|
+
fg="yellow",
|
|
304
|
+
bold=True,
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
click.echo("")
|
|
308
|
+
click.echo("Claude needs your input to resolve this conflict.")
|
|
309
|
+
click.echo("Run conflict resolution interactively:")
|
|
310
|
+
click.echo("")
|
|
311
|
+
click.echo(click.style(" claude /erk:fix-conflicts", fg="cyan"))
|
|
312
|
+
click.echo("")
|
|
313
|
+
return FixConflictsResult(
|
|
314
|
+
success=False,
|
|
315
|
+
requires_interactive=True,
|
|
316
|
+
)
|
|
317
|
+
# Tool summaries with icon
|
|
318
|
+
click.echo(click.style(f" ⚙️ {summary}", fg="cyan", dim=True))
|
|
319
|
+
case SpinnerUpdateEvent(status=status):
|
|
320
|
+
if status != last_spinner:
|
|
321
|
+
click.echo(click.style(f" ⏳ {status}", dim=True))
|
|
322
|
+
last_spinner = status
|
|
323
|
+
case ErrorEvent(message=msg):
|
|
324
|
+
click.echo(click.style(f" ❌ {msg}", fg="red"))
|
|
325
|
+
error_message = msg
|
|
326
|
+
success = False
|
|
327
|
+
case NoOutputEvent(diagnostic=diag):
|
|
328
|
+
click.echo(click.style(f" ⚠️ {diag}", fg="yellow"))
|
|
329
|
+
error_message = diag
|
|
330
|
+
success = False
|
|
331
|
+
case NoTurnsEvent(diagnostic=diag):
|
|
332
|
+
click.echo(click.style(f" ⚠️ {diag}", fg="yellow"))
|
|
333
|
+
error_message = diag
|
|
334
|
+
success = False
|
|
335
|
+
case ProcessErrorEvent(message=msg):
|
|
336
|
+
click.echo(click.style(f" ❌ {msg}", fg="red"))
|
|
337
|
+
error_message = msg
|
|
338
|
+
success = False
|
|
339
|
+
case PrUrlEvent() | PrNumberEvent() | PrTitleEvent() | IssueNumberEvent():
|
|
340
|
+
pass # PR metadata not relevant for fix-conflicts
|
|
341
|
+
|
|
342
|
+
# Check for no-work-events failure mode
|
|
343
|
+
if success and not has_work_events:
|
|
344
|
+
success = False
|
|
345
|
+
error_message = (
|
|
346
|
+
"Claude completed without producing any output - "
|
|
347
|
+
"check hooks or run 'claude /erk:fix-conflicts' directly to debug"
|
|
348
|
+
)
|
|
349
|
+
click.echo(click.style(f" ⚠️ {error_message}", fg="yellow"))
|
|
350
|
+
|
|
351
|
+
# Calculate duration and print end marker
|
|
352
|
+
duration = time.time() - start_time
|
|
353
|
+
duration_str = format_duration(duration)
|
|
354
|
+
|
|
355
|
+
click.echo("")
|
|
356
|
+
if success:
|
|
357
|
+
click.echo(click.style(f"--- Done ({duration_str}) ---", fg="green", bold=True))
|
|
358
|
+
else:
|
|
359
|
+
click.echo(click.style(f"--- Failed ({duration_str}) ---", fg="red", bold=True))
|
|
360
|
+
|
|
361
|
+
return FixConflictsResult(success=success, error_message=error_message)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# work config for this repository
|
|
2
|
+
# Available template variables: {worktree_path}, {repo_root}, {name}
|
|
3
|
+
|
|
4
|
+
[env]
|
|
5
|
+
# EXAMPLE_KEY = "{worktree_path}"
|
|
6
|
+
|
|
7
|
+
[post_create]
|
|
8
|
+
# shell = "bash"
|
|
9
|
+
# commands = [
|
|
10
|
+
# "uv venv",
|
|
11
|
+
# "uv run make dev_install",
|
|
12
|
+
# ]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Prompt Hooks
|
|
2
|
+
|
|
3
|
+
Prompt hooks are markdown files that provide instructions to AI agents at specific points in your erk workflows.
|
|
4
|
+
|
|
5
|
+
## What Are Prompt Hooks?
|
|
6
|
+
|
|
7
|
+
Unlike Claude Code hooks (which execute shell commands at lifecycle events), prompt hooks are **AI-readable documentation** that:
|
|
8
|
+
|
|
9
|
+
- Execute at specific workflow points (e.g., after plan implementation)
|
|
10
|
+
- Provide project-specific instructions to AI agents
|
|
11
|
+
- Customize AI behavior without code changes
|
|
12
|
+
- Are version-controlled with your project
|
|
13
|
+
|
|
14
|
+
## Available Hooks
|
|
15
|
+
|
|
16
|
+
| Hook File | Fires When | Purpose |
|
|
17
|
+
| --------------------------- | ---------------------------------------------------- | ----------------------------------------- |
|
|
18
|
+
| `post-plan-implement-ci.md` | After `/erk:plan-implement` completes implementation | Define CI workflow and iteration strategy |
|
|
19
|
+
|
|
20
|
+
## Creating a Prompt Hook
|
|
21
|
+
|
|
22
|
+
1. Create a markdown file in `.erk/prompt-hooks/` with the appropriate name
|
|
23
|
+
2. Write instructions for the AI agent in imperative mood
|
|
24
|
+
3. Include specific commands, tools to load, and success criteria
|
|
25
|
+
4. Reference skills to load (e.g., `Load the \`ci-iteration\` skill`)
|
|
26
|
+
|
|
27
|
+
### Example: post-plan-implement-ci.md
|
|
28
|
+
|
|
29
|
+
\`\`\`markdown
|
|
30
|
+
|
|
31
|
+
# Post-Implementation CI
|
|
32
|
+
|
|
33
|
+
Run CI validation after plan implementation using \`make fast-ci\`.
|
|
34
|
+
|
|
35
|
+
Load the \`ci-iteration\` skill for the iterative fix workflow.
|
|
36
|
+
|
|
37
|
+
## Iteration Process
|
|
38
|
+
|
|
39
|
+
1. Run \`make fast-ci\` via devrun agent
|
|
40
|
+
2. If checks fail: apply targeted fixes
|
|
41
|
+
3. Re-run CI (max 5 iterations)
|
|
42
|
+
4. On success: proceed to PR creation
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
## Best Practices
|
|
46
|
+
|
|
47
|
+
- **Be specific:** Provide exact commands and tool names
|
|
48
|
+
- **Define success:** Clear exit criteria prevent infinite loops
|
|
49
|
+
- **Reference skills:** Load relevant skills for specialized workflows
|
|
50
|
+
- **Version control:** Commit hooks alongside code
|
|
51
|
+
- **Document context:** Explain WHY the workflow exists
|
|
52
|
+
|
|
53
|
+
## Customization
|
|
54
|
+
|
|
55
|
+
These are **erk-defined hooks that you can customize** for your project's needs:
|
|
56
|
+
|
|
57
|
+
- Modify existing hooks to match your CI/CD setup
|
|
58
|
+
- Add project-specific validation steps
|
|
59
|
+
- Reference your custom skills and commands
|
|
60
|
+
- Adjust iteration limits and error handling
|
|
61
|
+
|
|
62
|
+
## Future Hooks
|
|
63
|
+
|
|
64
|
+
Potential hooks for future workflows (create these as needed):
|
|
65
|
+
|
|
66
|
+
- `pre-plan-implement.md` - Setup before implementation starts
|
|
67
|
+
- `post-pr-create.md` - After PR creation workflow
|
|
68
|
+
- `pre-worktree-create.md` - Validation before worktree setup
|
erk/cli/script_output.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Output utilities for exec scripts.
|
|
2
|
+
|
|
3
|
+
Exec scripts communicate results via JSON to stdout. This module provides
|
|
4
|
+
consistent error handling that outputs structured JSON rather than raising
|
|
5
|
+
exceptions, supporting shell scripting patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import NoReturn
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def exit_with_error(error_type: str, message: str) -> NoReturn:
|
|
15
|
+
"""Output JSON error and exit with code 0.
|
|
16
|
+
|
|
17
|
+
Exec commands exit with 0 even on error to support || true patterns
|
|
18
|
+
in shell scripts. The error is communicated via JSON output.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
error_type: Machine-readable error category (e.g., "no_pr_for_branch")
|
|
22
|
+
message: Human-readable error message
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
SystemExit: Always exits with code 0 after printing JSON
|
|
26
|
+
"""
|
|
27
|
+
error_json = json.dumps(
|
|
28
|
+
{"success": False, "error_type": error_type, "message": message},
|
|
29
|
+
indent=2,
|
|
30
|
+
)
|
|
31
|
+
click.echo(error_json)
|
|
32
|
+
raise SystemExit(0)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Erk shell integration for bash
|
|
2
|
+
# This function wraps the erk CLI to provide seamless worktree switching
|
|
3
|
+
|
|
4
|
+
erk() {
|
|
5
|
+
# Don't intercept if we're doing shell completion
|
|
6
|
+
[ -n "$_ERK_COMPLETE" ] && { command erk "$@"; return; }
|
|
7
|
+
|
|
8
|
+
local script_path exit_status
|
|
9
|
+
script_path=$(ERK_SHELL=bash command erk __shell "$@")
|
|
10
|
+
exit_status=$?
|
|
11
|
+
|
|
12
|
+
# Passthrough mode: run the original command directly
|
|
13
|
+
[ "$script_path" = "__ERK_PASSTHROUGH__" ] && { command erk "$@"; return; }
|
|
14
|
+
|
|
15
|
+
# Source the script file if it exists, regardless of exit code.
|
|
16
|
+
# This matches Python handler logic: use script even if command had errors.
|
|
17
|
+
# The script contains important state changes (like cd to target dir).
|
|
18
|
+
if [ -n "$script_path" ] && [ -f "$script_path" ]; then
|
|
19
|
+
source "$script_path"
|
|
20
|
+
local source_exit=$?
|
|
21
|
+
|
|
22
|
+
# Clean up unless ERK_KEEP_SCRIPTS is set
|
|
23
|
+
if [ -z "$ERK_KEEP_SCRIPTS" ]; then
|
|
24
|
+
rm -f "$script_path"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
return $source_exit
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Only return exit_status if no script was provided
|
|
31
|
+
[ $exit_status -ne 0 ] && return $exit_status
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Erk shell integration for fish
|
|
2
|
+
# This function wraps the erk CLI to provide seamless worktree switching
|
|
3
|
+
|
|
4
|
+
function erk
|
|
5
|
+
# Don't intercept if we're doing shell completion
|
|
6
|
+
if set -q _ERK_COMPLETE
|
|
7
|
+
command erk $argv
|
|
8
|
+
return
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
set -l script_path (env ERK_SHELL=fish command erk __shell $argv)
|
|
12
|
+
set -l exit_status $status
|
|
13
|
+
|
|
14
|
+
# Passthrough mode
|
|
15
|
+
if test "$script_path" = "__ERK_PASSTHROUGH__"
|
|
16
|
+
command erk $argv
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Source the script file if it exists, regardless of exit code.
|
|
21
|
+
# This matches Python handler logic: use script even if command had errors.
|
|
22
|
+
# The script contains important state changes (like cd to target dir).
|
|
23
|
+
if test -n "$script_path" -a -f "$script_path"
|
|
24
|
+
source "$script_path"
|
|
25
|
+
set -l source_exit $status
|
|
26
|
+
|
|
27
|
+
# Clean up unless ERK_KEEP_SCRIPTS is set
|
|
28
|
+
if not set -q ERK_KEEP_SCRIPTS
|
|
29
|
+
rm -f "$script_path"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return $source_exit
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Only return exit_status if no script was provided
|
|
36
|
+
if test $exit_status -ne 0
|
|
37
|
+
return $exit_status
|
|
38
|
+
end
|
|
39
|
+
end
|