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,103 @@
|
|
|
1
|
+
"""Extract plan from Claude session and create GitHub issue.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
erk exec create-issue-from-session [--session-id SESSION_ID]
|
|
5
|
+
|
|
6
|
+
This command combines plan extraction from Claude session files with GitHub
|
|
7
|
+
issue creation. It extracts the latest ExitPlanMode plan, ensures the erk-plan
|
|
8
|
+
label exists, and creates a GitHub issue with the plan content.
|
|
9
|
+
|
|
10
|
+
SCHEMA VERSION 2: This command uses the new two-step creation flow:
|
|
11
|
+
1. Create issue with metadata-only body (using format_plan_header_body())
|
|
12
|
+
2. Add first comment with plan content (using format_plan_content_comment())
|
|
13
|
+
|
|
14
|
+
Output:
|
|
15
|
+
JSON result on stdout: {"success": true, "issue_number": N, "issue_url": "..."}
|
|
16
|
+
Error messages on stderr with exit code 1 on failure
|
|
17
|
+
|
|
18
|
+
Exit Codes:
|
|
19
|
+
0: Success - issue created
|
|
20
|
+
1: Error - no plan found, gh CLI not available, or other error
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
|
|
25
|
+
import click
|
|
26
|
+
|
|
27
|
+
from erk_shared.context.helpers import (
|
|
28
|
+
require_claude_installation,
|
|
29
|
+
require_cwd,
|
|
30
|
+
require_repo_root,
|
|
31
|
+
)
|
|
32
|
+
from erk_shared.context.helpers import (
|
|
33
|
+
require_issues as require_github_issues,
|
|
34
|
+
)
|
|
35
|
+
from erk_shared.github.plan_issues import create_plan_issue
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.command(name="create-issue-from-session")
|
|
39
|
+
@click.option(
|
|
40
|
+
"--session-id",
|
|
41
|
+
help="Session ID to search within (optional, searches all sessions if not provided)",
|
|
42
|
+
)
|
|
43
|
+
@click.pass_context
|
|
44
|
+
def create_issue_from_session(ctx: click.Context, session_id: str | None) -> None:
|
|
45
|
+
"""Extract plan from Claude session and create GitHub issue.
|
|
46
|
+
|
|
47
|
+
Combines plan extraction with GitHub issue creation in a single operation.
|
|
48
|
+
|
|
49
|
+
Schema Version 2 format:
|
|
50
|
+
- Issue body: metadata-only (schema_version, created_at, created_by, worktree_name)
|
|
51
|
+
- First comment: plan content wrapped in markers
|
|
52
|
+
"""
|
|
53
|
+
# Get dependencies from context
|
|
54
|
+
github = require_github_issues(ctx)
|
|
55
|
+
repo_root = require_repo_root(ctx)
|
|
56
|
+
cwd = require_cwd(ctx)
|
|
57
|
+
claude_installation = require_claude_installation(ctx)
|
|
58
|
+
|
|
59
|
+
# Extract latest plan from session
|
|
60
|
+
plan_text = claude_installation.get_latest_plan(cwd, session_id=session_id)
|
|
61
|
+
|
|
62
|
+
if not plan_text:
|
|
63
|
+
result = {"success": False, "error": "No plan found in Claude session files"}
|
|
64
|
+
click.echo(json.dumps(result))
|
|
65
|
+
raise SystemExit(1)
|
|
66
|
+
|
|
67
|
+
# Use consolidated create_plan_issue for the entire workflow
|
|
68
|
+
result = create_plan_issue(
|
|
69
|
+
github_issues=github,
|
|
70
|
+
repo_root=repo_root,
|
|
71
|
+
plan_content=plan_text,
|
|
72
|
+
title=None,
|
|
73
|
+
plan_type=None,
|
|
74
|
+
extra_labels=None,
|
|
75
|
+
title_suffix=None,
|
|
76
|
+
source_plan_issues=None,
|
|
77
|
+
extraction_session_ids=None,
|
|
78
|
+
source_repo=None,
|
|
79
|
+
objective_issue=None,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if not result.success:
|
|
83
|
+
if result.issue_number is not None:
|
|
84
|
+
# Partial success - issue created but comment failed
|
|
85
|
+
output = {
|
|
86
|
+
"success": False,
|
|
87
|
+
"error": result.error,
|
|
88
|
+
"issue_number": result.issue_number,
|
|
89
|
+
"issue_url": result.issue_url,
|
|
90
|
+
}
|
|
91
|
+
else:
|
|
92
|
+
output = {"success": False, "error": result.error}
|
|
93
|
+
click.echo(json.dumps(output))
|
|
94
|
+
raise SystemExit(1)
|
|
95
|
+
|
|
96
|
+
# Return success result
|
|
97
|
+
output = {
|
|
98
|
+
"success": True,
|
|
99
|
+
"issue_number": result.issue_number,
|
|
100
|
+
"issue_url": result.issue_url,
|
|
101
|
+
"title": result.title,
|
|
102
|
+
}
|
|
103
|
+
click.echo(json.dumps(output))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Create GitHub issue from plan content (via stdin) with erk-plan label.
|
|
2
|
+
|
|
3
|
+
This exec command handles the complete workflow for creating a plan:
|
|
4
|
+
1. Read plan from stdin
|
|
5
|
+
2. Extract title from plan
|
|
6
|
+
3. Ensure erk-plan label exists
|
|
7
|
+
4. Create GitHub issue with plan body and label
|
|
8
|
+
5. Return structured JSON result
|
|
9
|
+
|
|
10
|
+
This replaces the complex shell orchestration in the slash command with a single,
|
|
11
|
+
well-tested Python command that uses the ABC interface for GitHub operations.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from erk_shared.context.helpers import require_issues as require_github_issues
|
|
20
|
+
from erk_shared.context.helpers import require_repo_root
|
|
21
|
+
from erk_shared.github.metadata.core import format_plan_issue_body
|
|
22
|
+
from erk_shared.plan_utils import extract_title_from_plan
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command(name="create-plan-from-context")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def create_plan_from_context(ctx: click.Context) -> None:
|
|
28
|
+
"""Create GitHub issue from plan content with erk-plan label.
|
|
29
|
+
|
|
30
|
+
Reads plan content from stdin, extracts title, ensures erk-plan label exists,
|
|
31
|
+
creates issue with collapsible plan body and execution commands, and returns JSON result.
|
|
32
|
+
|
|
33
|
+
Workflow:
|
|
34
|
+
1. Create issue with plan body wrapped in collapsible metadata block
|
|
35
|
+
2. Update issue body to include execution commands (using returned issue number)
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
echo "$plan" | erk exec create-plan-from-context
|
|
39
|
+
|
|
40
|
+
Exit Codes:
|
|
41
|
+
0: Success
|
|
42
|
+
1: Error (empty plan, gh failure, etc.)
|
|
43
|
+
|
|
44
|
+
Output:
|
|
45
|
+
JSON object: {"success": true, "issue_number": 123, "issue_url": "..."}
|
|
46
|
+
"""
|
|
47
|
+
# Get GitHub Issues from context (LBYL check in helper)
|
|
48
|
+
github = require_github_issues(ctx)
|
|
49
|
+
repo_root = require_repo_root(ctx)
|
|
50
|
+
|
|
51
|
+
# Read plan from stdin
|
|
52
|
+
plan = sys.stdin.read()
|
|
53
|
+
|
|
54
|
+
# Validate plan not empty
|
|
55
|
+
if not plan or not plan.strip():
|
|
56
|
+
click.echo("Error: Empty plan content received", err=True)
|
|
57
|
+
raise SystemExit(1)
|
|
58
|
+
|
|
59
|
+
# Extract title (pure function call)
|
|
60
|
+
title = extract_title_from_plan(plan)
|
|
61
|
+
|
|
62
|
+
# Initial body: just the plan content (without commands, since we don't have issue number yet)
|
|
63
|
+
# We'll update it after creation with the full formatted body including commands
|
|
64
|
+
initial_body = plan.strip()
|
|
65
|
+
|
|
66
|
+
# Ensure label exists (ABC interface)
|
|
67
|
+
try:
|
|
68
|
+
github.ensure_label_exists(
|
|
69
|
+
repo_root=repo_root,
|
|
70
|
+
label="erk-plan",
|
|
71
|
+
description="Implementation plan for manual execution",
|
|
72
|
+
color="0E8A16",
|
|
73
|
+
)
|
|
74
|
+
except RuntimeError as e:
|
|
75
|
+
click.echo(f"Error: Failed to ensure label exists: {e}", err=True)
|
|
76
|
+
raise SystemExit(1) from e
|
|
77
|
+
|
|
78
|
+
# Create issue (ABC interface with EAFP pattern)
|
|
79
|
+
# Add [erk-plan] suffix to title for visibility
|
|
80
|
+
issue_title = f"{title} [erk-plan]"
|
|
81
|
+
try:
|
|
82
|
+
result = github.create_issue(repo_root, issue_title, initial_body, ["erk-plan"])
|
|
83
|
+
except RuntimeError as e:
|
|
84
|
+
click.echo(f"Error: Failed to create GitHub issue: {e}", err=True)
|
|
85
|
+
raise SystemExit(1) from e
|
|
86
|
+
|
|
87
|
+
# Now that we have the issue number, format the complete body with commands
|
|
88
|
+
formatted_body = format_plan_issue_body(plan.strip(), result.number)
|
|
89
|
+
|
|
90
|
+
# Update the issue body with the formatted version
|
|
91
|
+
try:
|
|
92
|
+
github.update_issue_body(repo_root, result.number, formatted_body)
|
|
93
|
+
except RuntimeError as e:
|
|
94
|
+
click.echo(f"Error: Failed to update issue body: {e}", err=True)
|
|
95
|
+
raise SystemExit(1) from e
|
|
96
|
+
|
|
97
|
+
# Output structured JSON
|
|
98
|
+
output = {
|
|
99
|
+
"success": True,
|
|
100
|
+
"issue_number": result.number,
|
|
101
|
+
"issue_url": result.url,
|
|
102
|
+
}
|
|
103
|
+
click.echo(json.dumps(output))
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Create .worker-impl/ folder from GitHub issue with plan content.
|
|
2
|
+
|
|
3
|
+
This exec command fetches a plan from a GitHub issue and creates the .worker-impl/
|
|
4
|
+
folder structure, providing a testable alternative to inline workflow scripts.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
erk exec create-worker-impl-from-issue <issue-number>
|
|
8
|
+
|
|
9
|
+
Output:
|
|
10
|
+
Structured JSON output with success status and folder details
|
|
11
|
+
|
|
12
|
+
Exit Codes:
|
|
13
|
+
0: Success (.worker-impl/ folder created)
|
|
14
|
+
1: Error (issue not found, plan fetch failed, folder creation failed)
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
$ erk exec create-worker-impl-from-issue 1028
|
|
18
|
+
{"success": true, "worker_impl_path": "/path/to/.worker-impl", "issue_number": 1028}
|
|
19
|
+
|
|
20
|
+
$ erk exec create-worker-impl-from-issue 999
|
|
21
|
+
{"success": false, "error": "issue_not_found", "message": "..."}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
import click
|
|
28
|
+
|
|
29
|
+
from erk_shared.gateway.time.real import RealTime
|
|
30
|
+
from erk_shared.github.issues import RealGitHubIssues
|
|
31
|
+
from erk_shared.plan_store.github import GitHubPlanStore
|
|
32
|
+
from erk_shared.worker_impl_folder import create_worker_impl_folder
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.command(name="create-worker-impl-from-issue")
|
|
36
|
+
@click.argument("issue_number", type=int)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--repo-root",
|
|
39
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
40
|
+
default=None,
|
|
41
|
+
help="Repository root directory (defaults to current directory)",
|
|
42
|
+
)
|
|
43
|
+
def create_worker_impl_from_issue(
|
|
44
|
+
issue_number: int,
|
|
45
|
+
repo_root: Path | None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Create .worker-impl/ folder from GitHub issue with plan content.
|
|
48
|
+
|
|
49
|
+
Fetches plan content from GitHub issue and creates .worker-impl/ folder structure
|
|
50
|
+
with plan.md, issue.json, and metadata.
|
|
51
|
+
|
|
52
|
+
ISSUE_NUMBER: GitHub issue number containing the plan
|
|
53
|
+
"""
|
|
54
|
+
# Default to current directory if not specified
|
|
55
|
+
if repo_root is None:
|
|
56
|
+
repo_root = Path.cwd()
|
|
57
|
+
|
|
58
|
+
# Direct instantiation of required dependencies (avoids erk import)
|
|
59
|
+
# This allows the command to work when run via erk kit exec without uv
|
|
60
|
+
time = RealTime()
|
|
61
|
+
github_issues = RealGitHubIssues(target_repo=None)
|
|
62
|
+
plan_store = GitHubPlanStore(github_issues, time)
|
|
63
|
+
|
|
64
|
+
# Fetch plan from GitHub (raises RuntimeError if not found)
|
|
65
|
+
try:
|
|
66
|
+
plan = plan_store.get_plan(repo_root, str(issue_number))
|
|
67
|
+
except RuntimeError as e:
|
|
68
|
+
error_output = {
|
|
69
|
+
"success": False,
|
|
70
|
+
"error": "plan_not_found",
|
|
71
|
+
"message": f"Could not fetch plan for issue #{issue_number}: {e}. "
|
|
72
|
+
f"Ensure issue has erk-plan label and plan content.",
|
|
73
|
+
}
|
|
74
|
+
click.echo(json.dumps(error_output), err=True)
|
|
75
|
+
raise SystemExit(1) from e
|
|
76
|
+
|
|
77
|
+
# Create .worker-impl/ folder with plan content
|
|
78
|
+
worker_impl_path = repo_root / ".worker-impl"
|
|
79
|
+
create_worker_impl_folder(
|
|
80
|
+
plan_content=plan.body,
|
|
81
|
+
issue_number=issue_number,
|
|
82
|
+
issue_url=plan.url,
|
|
83
|
+
repo_root=repo_root,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Output structured success result
|
|
87
|
+
output = {
|
|
88
|
+
"success": True,
|
|
89
|
+
"worker_impl_path": str(worker_impl_path),
|
|
90
|
+
"issue_number": issue_number,
|
|
91
|
+
"issue_url": plan.url,
|
|
92
|
+
}
|
|
93
|
+
click.echo(json.dumps(output))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Detect whether repo uses `main` or `master` as trunk branch.
|
|
3
|
+
|
|
4
|
+
This command checks the remote (origin) for the existence of trunk branches,
|
|
5
|
+
suitable for CI environments where local branches may not be available.
|
|
6
|
+
|
|
7
|
+
This replaces bash-based detection in GitHub Actions workflows:
|
|
8
|
+
```bash
|
|
9
|
+
if git ls-remote --heads origin main | grep -q main; then
|
|
10
|
+
echo "trunk_branch=main"
|
|
11
|
+
elif git ls-remote --heads origin master | grep -q master; then
|
|
12
|
+
echo "trunk_branch=master"
|
|
13
|
+
else
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
erk exec detect-trunk-branch
|
|
20
|
+
|
|
21
|
+
Output:
|
|
22
|
+
JSON object with success status and detected trunk branch
|
|
23
|
+
|
|
24
|
+
Exit Codes:
|
|
25
|
+
0: Success (trunk branch detected)
|
|
26
|
+
1: Error (neither main nor master exists on remote)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
$ erk exec detect-trunk-branch
|
|
30
|
+
{
|
|
31
|
+
"success": true,
|
|
32
|
+
"trunk_branch": "main"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$ erk exec detect-trunk-branch # in repo with master only
|
|
36
|
+
{
|
|
37
|
+
"success": true,
|
|
38
|
+
"trunk_branch": "master"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$ erk exec detect-trunk-branch # in repo without main/master
|
|
42
|
+
{
|
|
43
|
+
"success": false,
|
|
44
|
+
"error": "trunk_not_found",
|
|
45
|
+
"message": "Could not detect trunk branch (neither 'main' nor 'master' exists on origin)"
|
|
46
|
+
}
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
import json
|
|
50
|
+
from dataclasses import asdict, dataclass
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
from typing import Literal
|
|
53
|
+
|
|
54
|
+
import click
|
|
55
|
+
|
|
56
|
+
from erk_shared.context.helpers import require_git, require_repo_root
|
|
57
|
+
from erk_shared.git.abc import Git
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class DetectedTrunk:
|
|
62
|
+
"""Success result with detected trunk branch."""
|
|
63
|
+
|
|
64
|
+
success: bool
|
|
65
|
+
trunk_branch: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class DetectionError:
|
|
70
|
+
"""Error result when trunk branch cannot be detected."""
|
|
71
|
+
|
|
72
|
+
success: bool
|
|
73
|
+
error: Literal["trunk-not-found", "git-error"]
|
|
74
|
+
message: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _detect_trunk_branch_impl(git: Git, repo_root: Path) -> DetectedTrunk | DetectionError:
|
|
78
|
+
"""Detect trunk branch by checking remote for main or master.
|
|
79
|
+
|
|
80
|
+
Checks origin remote for 'main' first, then 'master'. Returns error
|
|
81
|
+
if neither exists.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
git: Git interface for operations
|
|
85
|
+
repo_root: Path to the git repository root
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
DetectedTrunk on success, DetectionError if neither branch found
|
|
89
|
+
"""
|
|
90
|
+
# Check main first (modern convention), then master (legacy convention)
|
|
91
|
+
for candidate in ["main", "master"]:
|
|
92
|
+
if git.branch_exists_on_remote(repo_root, "origin", candidate):
|
|
93
|
+
return DetectedTrunk(success=True, trunk_branch=candidate)
|
|
94
|
+
|
|
95
|
+
# Neither found
|
|
96
|
+
return DetectionError(
|
|
97
|
+
success=False,
|
|
98
|
+
error="trunk-not-found",
|
|
99
|
+
message="Could not detect trunk branch (neither 'main' nor 'master' exists on origin)",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@click.command(name="detect-trunk-branch")
|
|
104
|
+
@click.pass_context
|
|
105
|
+
def detect_trunk_branch(ctx: click.Context) -> None:
|
|
106
|
+
"""Detect whether repo uses main or master as trunk branch.
|
|
107
|
+
|
|
108
|
+
Queries the origin remote to check for trunk branch existence.
|
|
109
|
+
Checks for 'main' first, then 'master'. Exits with error if neither exists.
|
|
110
|
+
"""
|
|
111
|
+
git = require_git(ctx)
|
|
112
|
+
repo_root = require_repo_root(ctx)
|
|
113
|
+
|
|
114
|
+
result = _detect_trunk_branch_impl(git, repo_root)
|
|
115
|
+
|
|
116
|
+
# Output JSON result
|
|
117
|
+
click.echo(json.dumps(asdict(result), indent=2))
|
|
118
|
+
|
|
119
|
+
# Exit with error code if detection failed
|
|
120
|
+
if isinstance(result, DetectionError):
|
|
121
|
+
raise SystemExit(1)
|