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,294 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Update PR body with AI-generated summary and footer.
|
|
3
|
+
|
|
4
|
+
This command generates a PR summary from the diff using Claude, then updates
|
|
5
|
+
the PR body with the summary, optional workflow link, and standardized footer.
|
|
6
|
+
|
|
7
|
+
This combines generate-pr-summary + footer construction + gh pr edit in one step,
|
|
8
|
+
replacing ~30 lines of bash in GitHub Actions workflows.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
erk exec ci-update-pr-body \\
|
|
12
|
+
--issue-number 123 \\
|
|
13
|
+
[--run-id 456789] \\
|
|
14
|
+
[--run-url https://github.com/owner/repo/actions/runs/456789]
|
|
15
|
+
|
|
16
|
+
Output:
|
|
17
|
+
JSON object with success status
|
|
18
|
+
|
|
19
|
+
Exit Codes:
|
|
20
|
+
0: Success (PR body updated)
|
|
21
|
+
1: Error (no PR for branch, empty diff, Claude failure, or GitHub API failed)
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
$ erk exec ci-update-pr-body --issue-number 123
|
|
25
|
+
{
|
|
26
|
+
"success": true,
|
|
27
|
+
"pr_number": 789
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$ erk exec ci-update-pr-body \\
|
|
31
|
+
--issue-number 123 \\
|
|
32
|
+
--run-id 456789 \\
|
|
33
|
+
--run-url https://github.com/owner/repo/actions/runs/456789
|
|
34
|
+
{
|
|
35
|
+
"success": true,
|
|
36
|
+
"pr_number": 789
|
|
37
|
+
}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
from dataclasses import asdict, dataclass
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from typing import Literal
|
|
44
|
+
|
|
45
|
+
import click
|
|
46
|
+
|
|
47
|
+
from erk.cli.config import load_config
|
|
48
|
+
from erk_shared.context.helpers import (
|
|
49
|
+
require_git,
|
|
50
|
+
require_github,
|
|
51
|
+
require_prompt_executor,
|
|
52
|
+
require_repo_root,
|
|
53
|
+
)
|
|
54
|
+
from erk_shared.gateway.gt.prompts import COMMIT_MESSAGE_SYSTEM_PROMPT, truncate_diff
|
|
55
|
+
from erk_shared.git.abc import Git
|
|
56
|
+
from erk_shared.github.abc import GitHub
|
|
57
|
+
from erk_shared.github.pr_footer import build_pr_body_footer, build_remote_execution_note
|
|
58
|
+
from erk_shared.github.types import PRNotFound
|
|
59
|
+
from erk_shared.prompt_executor import PromptExecutor
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class UpdateSuccess:
|
|
64
|
+
"""Success result when PR body is updated."""
|
|
65
|
+
|
|
66
|
+
success: bool
|
|
67
|
+
pr_number: int
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class UpdateError:
|
|
72
|
+
"""Error result when PR body update fails."""
|
|
73
|
+
|
|
74
|
+
success: bool
|
|
75
|
+
error: Literal[
|
|
76
|
+
"pr-not-found",
|
|
77
|
+
"empty-diff",
|
|
78
|
+
"diff-fetch-failed",
|
|
79
|
+
"claude-execution-failed",
|
|
80
|
+
"claude-empty-output",
|
|
81
|
+
"github-api-failed",
|
|
82
|
+
]
|
|
83
|
+
message: str
|
|
84
|
+
stderr: str | None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _build_prompt(diff_content: str, current_branch: str, parent_branch: str) -> str:
|
|
88
|
+
"""Build prompt for PR summary generation.
|
|
89
|
+
|
|
90
|
+
Note: We deliberately do NOT include commit messages here. The commit messages
|
|
91
|
+
may contain info about .worker-impl/ deletions that don't appear in the final PR diff.
|
|
92
|
+
"""
|
|
93
|
+
context_section = f"""## Context
|
|
94
|
+
|
|
95
|
+
- Current branch: {current_branch}
|
|
96
|
+
- Parent branch: {parent_branch}"""
|
|
97
|
+
|
|
98
|
+
return f"""{COMMIT_MESSAGE_SYSTEM_PROMPT}
|
|
99
|
+
|
|
100
|
+
{context_section}
|
|
101
|
+
|
|
102
|
+
## Diff
|
|
103
|
+
|
|
104
|
+
```diff
|
|
105
|
+
{diff_content}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Generate a commit message for this diff:"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _build_pr_body(
|
|
112
|
+
summary: str,
|
|
113
|
+
pr_number: int,
|
|
114
|
+
issue_number: int,
|
|
115
|
+
run_id: str | None,
|
|
116
|
+
run_url: str | None,
|
|
117
|
+
plans_repo: str | None,
|
|
118
|
+
) -> str:
|
|
119
|
+
"""Build the full PR body with summary, optional workflow link, and footer.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
summary: AI-generated PR summary
|
|
123
|
+
pr_number: PR number for checkout instructions
|
|
124
|
+
issue_number: Issue number to close on merge
|
|
125
|
+
run_id: Optional workflow run ID
|
|
126
|
+
run_url: Optional workflow run URL
|
|
127
|
+
plans_repo: Target repo in "owner/repo" format for cross-repo plans
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Formatted PR body markdown
|
|
131
|
+
"""
|
|
132
|
+
parts = [f"## Summary\n\n{summary}"]
|
|
133
|
+
|
|
134
|
+
# Add workflow link if provided
|
|
135
|
+
if run_id is not None and run_url is not None:
|
|
136
|
+
parts.append(build_remote_execution_note(run_id, run_url))
|
|
137
|
+
|
|
138
|
+
# Add footer with checkout instructions
|
|
139
|
+
parts.append(
|
|
140
|
+
build_pr_body_footer(pr_number=pr_number, issue_number=issue_number, plans_repo=plans_repo)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return "\n".join(parts)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _update_pr_body_impl(
|
|
147
|
+
git: Git,
|
|
148
|
+
github: GitHub,
|
|
149
|
+
executor: PromptExecutor,
|
|
150
|
+
repo_root: Path,
|
|
151
|
+
issue_number: int,
|
|
152
|
+
run_id: str | None,
|
|
153
|
+
run_url: str | None,
|
|
154
|
+
plans_repo: str | None,
|
|
155
|
+
) -> UpdateSuccess | UpdateError:
|
|
156
|
+
"""Implementation of PR body update.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
git: Git interface
|
|
160
|
+
github: GitHub interface
|
|
161
|
+
executor: PromptExecutor for Claude
|
|
162
|
+
repo_root: Repository root path
|
|
163
|
+
issue_number: Issue number to close on merge
|
|
164
|
+
run_id: Optional workflow run ID
|
|
165
|
+
run_url: Optional workflow run URL
|
|
166
|
+
plans_repo: Target repo in "owner/repo" format for cross-repo plans
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
UpdateSuccess on success, UpdateError on failure
|
|
170
|
+
"""
|
|
171
|
+
# Get current branch
|
|
172
|
+
current_branch = git.get_current_branch(repo_root)
|
|
173
|
+
if current_branch is None:
|
|
174
|
+
return UpdateError(
|
|
175
|
+
success=False,
|
|
176
|
+
error="pr-not-found",
|
|
177
|
+
message="Could not determine current branch",
|
|
178
|
+
stderr=None,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Get PR for branch
|
|
182
|
+
pr_result = github.get_pr_for_branch(repo_root, current_branch)
|
|
183
|
+
if isinstance(pr_result, PRNotFound):
|
|
184
|
+
return UpdateError(
|
|
185
|
+
success=False,
|
|
186
|
+
error="pr-not-found",
|
|
187
|
+
message=f"No PR found for branch {current_branch}",
|
|
188
|
+
stderr=None,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
pr_number = pr_result.number
|
|
192
|
+
|
|
193
|
+
# Get PR diff
|
|
194
|
+
try:
|
|
195
|
+
pr_diff = github.get_pr_diff(repo_root, pr_number)
|
|
196
|
+
except RuntimeError as e:
|
|
197
|
+
return UpdateError(
|
|
198
|
+
success=False,
|
|
199
|
+
error="diff-fetch-failed",
|
|
200
|
+
message=f"Failed to get PR diff: {e}",
|
|
201
|
+
stderr=None,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not pr_diff.strip():
|
|
205
|
+
return UpdateError(
|
|
206
|
+
success=False,
|
|
207
|
+
error="empty-diff",
|
|
208
|
+
message="PR diff is empty",
|
|
209
|
+
stderr=None,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Truncate diff if needed
|
|
213
|
+
diff_content, _was_truncated = truncate_diff(pr_diff)
|
|
214
|
+
|
|
215
|
+
# Get parent branch for context
|
|
216
|
+
parent_branch = git.detect_trunk_branch(repo_root)
|
|
217
|
+
|
|
218
|
+
# Generate summary using Claude
|
|
219
|
+
prompt = _build_prompt(diff_content, current_branch, parent_branch)
|
|
220
|
+
result = executor.execute_prompt(prompt, model="haiku", cwd=repo_root)
|
|
221
|
+
|
|
222
|
+
# Separate failure modes for better diagnostics
|
|
223
|
+
if not result.success:
|
|
224
|
+
stderr_preview = result.error[:500] if result.error else None
|
|
225
|
+
return UpdateError(
|
|
226
|
+
success=False,
|
|
227
|
+
error="claude-execution-failed",
|
|
228
|
+
message="Claude CLI returned non-zero exit code",
|
|
229
|
+
stderr=stderr_preview,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Check for empty output (success=True but no content)
|
|
233
|
+
if not result.output or not result.output.strip():
|
|
234
|
+
stderr_preview = result.error[:500] if result.error else None
|
|
235
|
+
return UpdateError(
|
|
236
|
+
success=False,
|
|
237
|
+
error="claude-empty-output",
|
|
238
|
+
message="Claude returned empty output (check API quota, rate limits, or token)",
|
|
239
|
+
stderr=stderr_preview,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Build full PR body
|
|
243
|
+
pr_body = _build_pr_body(result.output, pr_number, issue_number, run_id, run_url, plans_repo)
|
|
244
|
+
|
|
245
|
+
# Update PR body
|
|
246
|
+
try:
|
|
247
|
+
github.update_pr_body(repo_root, pr_number, pr_body)
|
|
248
|
+
except RuntimeError as e:
|
|
249
|
+
return UpdateError(
|
|
250
|
+
success=False,
|
|
251
|
+
error="github-api-failed",
|
|
252
|
+
message=f"Failed to update PR: {e}",
|
|
253
|
+
stderr=None,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return UpdateSuccess(success=True, pr_number=pr_number)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@click.command(name="ci-update-pr-body")
|
|
260
|
+
@click.option("--issue-number", type=int, required=True, help="Issue number to close on merge")
|
|
261
|
+
@click.option("--run-id", type=str, default=None, help="Optional workflow run ID")
|
|
262
|
+
@click.option("--run-url", type=str, default=None, help="Optional workflow run URL")
|
|
263
|
+
@click.pass_context
|
|
264
|
+
def ci_update_pr_body(
|
|
265
|
+
ctx: click.Context,
|
|
266
|
+
issue_number: int,
|
|
267
|
+
run_id: str | None,
|
|
268
|
+
run_url: str | None,
|
|
269
|
+
) -> None:
|
|
270
|
+
"""Update PR body with AI-generated summary and footer.
|
|
271
|
+
|
|
272
|
+
Generates a summary from the PR diff using Claude, then updates the PR body
|
|
273
|
+
with the summary, optional workflow link, and standardized footer with
|
|
274
|
+
checkout instructions.
|
|
275
|
+
"""
|
|
276
|
+
git = require_git(ctx)
|
|
277
|
+
github = require_github(ctx)
|
|
278
|
+
executor = require_prompt_executor(ctx)
|
|
279
|
+
repo_root = require_repo_root(ctx)
|
|
280
|
+
|
|
281
|
+
# Load config to get plans_repo
|
|
282
|
+
config = load_config(repo_root)
|
|
283
|
+
plans_repo = config.plans_repo
|
|
284
|
+
|
|
285
|
+
result = _update_pr_body_impl(
|
|
286
|
+
git, github, executor, repo_root, issue_number, run_id, run_url, plans_repo
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Output JSON result
|
|
290
|
+
click.echo(json.dumps(asdict(result), indent=2))
|
|
291
|
+
|
|
292
|
+
# Exit with error code if update failed
|
|
293
|
+
if isinstance(result, UpdateError):
|
|
294
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Create and push a branch for extraction documentation.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
erk exec create-extraction-branch \
|
|
5
|
+
--issue-number 123 \
|
|
6
|
+
--trunk-branch master
|
|
7
|
+
|
|
8
|
+
This command:
|
|
9
|
+
1. Checks out the trunk branch
|
|
10
|
+
2. Pulls latest changes
|
|
11
|
+
3. Creates a new branch named extraction-docs-{issue_number}
|
|
12
|
+
4. Pushes the branch with upstream tracking
|
|
13
|
+
|
|
14
|
+
Output:
|
|
15
|
+
JSON with success status and branch_name
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
from erk_shared.context.helpers import require_git, require_repo_root
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command(name="create-extraction-branch")
|
|
27
|
+
@click.option(
|
|
28
|
+
"--issue-number",
|
|
29
|
+
type=int,
|
|
30
|
+
required=True,
|
|
31
|
+
help="GitHub issue number",
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--trunk-branch",
|
|
35
|
+
type=str,
|
|
36
|
+
required=True,
|
|
37
|
+
help="Name of trunk branch (main/master)",
|
|
38
|
+
)
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def create_extraction_branch(
|
|
41
|
+
ctx: click.Context,
|
|
42
|
+
issue_number: int,
|
|
43
|
+
trunk_branch: str,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Create and push a branch for extraction documentation.
|
|
46
|
+
|
|
47
|
+
Creates a new branch from the trunk branch for implementing
|
|
48
|
+
documentation extraction from a GitHub issue.
|
|
49
|
+
"""
|
|
50
|
+
git = require_git(ctx)
|
|
51
|
+
repo_root = require_repo_root(ctx)
|
|
52
|
+
cwd = Path.cwd()
|
|
53
|
+
|
|
54
|
+
branch_name = f"extraction-docs-P{issue_number}"
|
|
55
|
+
|
|
56
|
+
# Check if branch already exists locally
|
|
57
|
+
local_branches = git.list_local_branches(repo_root)
|
|
58
|
+
if branch_name in local_branches:
|
|
59
|
+
click.echo(
|
|
60
|
+
json.dumps(
|
|
61
|
+
{
|
|
62
|
+
"success": False,
|
|
63
|
+
"error": f"Branch {branch_name} already exists locally",
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
raise SystemExit(1)
|
|
68
|
+
|
|
69
|
+
# Checkout trunk branch
|
|
70
|
+
try:
|
|
71
|
+
git.checkout_branch(cwd, trunk_branch)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
click.echo(
|
|
74
|
+
json.dumps(
|
|
75
|
+
{
|
|
76
|
+
"success": False,
|
|
77
|
+
"error": f"Failed to checkout {trunk_branch}: {e}",
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
raise SystemExit(1) from e
|
|
82
|
+
|
|
83
|
+
# Pull latest from trunk
|
|
84
|
+
try:
|
|
85
|
+
git.pull_branch(repo_root, "origin", trunk_branch, ff_only=True)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
click.echo(
|
|
88
|
+
json.dumps(
|
|
89
|
+
{
|
|
90
|
+
"success": False,
|
|
91
|
+
"error": f"Failed to pull {trunk_branch}: {e}",
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
raise SystemExit(1) from e
|
|
96
|
+
|
|
97
|
+
# Create and checkout new branch
|
|
98
|
+
try:
|
|
99
|
+
git.create_branch(cwd, branch_name, trunk_branch)
|
|
100
|
+
git.checkout_branch(cwd, branch_name)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
click.echo(
|
|
103
|
+
json.dumps(
|
|
104
|
+
{
|
|
105
|
+
"success": False,
|
|
106
|
+
"error": f"Failed to create branch {branch_name}: {e}",
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
raise SystemExit(1) from e
|
|
111
|
+
|
|
112
|
+
# Push with upstream tracking
|
|
113
|
+
try:
|
|
114
|
+
git.push_to_remote(cwd, "origin", branch_name, set_upstream=True)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
click.echo(
|
|
117
|
+
json.dumps(
|
|
118
|
+
{
|
|
119
|
+
"success": False,
|
|
120
|
+
"error": f"Failed to push branch {branch_name}: {e}",
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
raise SystemExit(1) from e
|
|
125
|
+
|
|
126
|
+
click.echo(
|
|
127
|
+
json.dumps(
|
|
128
|
+
{
|
|
129
|
+
"success": True,
|
|
130
|
+
"branch_name": branch_name,
|
|
131
|
+
"issue_number": issue_number,
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
create_extraction_branch()
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Create extraction plan issue from file or content with proper metadata.
|
|
2
|
+
|
|
3
|
+
Usage (new - preferred):
|
|
4
|
+
erk exec create-extraction-plan \
|
|
5
|
+
--plan-content="# Plan Title..." \
|
|
6
|
+
--session-id="abc123" \
|
|
7
|
+
--extraction-session-ids="abc123,def456"
|
|
8
|
+
|
|
9
|
+
Usage (legacy - file path):
|
|
10
|
+
erk exec create-extraction-plan \
|
|
11
|
+
--plan-file=".erk/scratch/<session-id>/extraction-plan.md" \
|
|
12
|
+
--source-plan-issues="123,456" \
|
|
13
|
+
--extraction-session-ids="abc123,def456"
|
|
14
|
+
|
|
15
|
+
The --plan-content option is preferred because it:
|
|
16
|
+
1. Automatically writes to .erk/scratch/<session-id>/extraction-plan.md
|
|
17
|
+
2. Prevents agents from accidentally using /tmp/
|
|
18
|
+
|
|
19
|
+
This command:
|
|
20
|
+
1. Creates GitHub issue with erk-plan + erk-extraction labels
|
|
21
|
+
2. Sets plan_type: extraction in plan-header metadata
|
|
22
|
+
3. Includes source_plan_issues and extraction_session_ids for tracking
|
|
23
|
+
|
|
24
|
+
Output:
|
|
25
|
+
JSON with success status, issue_number, and issue_url
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
import click
|
|
32
|
+
|
|
33
|
+
from erk_shared.context.helpers import (
|
|
34
|
+
require_cwd,
|
|
35
|
+
require_repo_root,
|
|
36
|
+
)
|
|
37
|
+
from erk_shared.context.helpers import (
|
|
38
|
+
require_issues as require_github_issues,
|
|
39
|
+
)
|
|
40
|
+
from erk_shared.github.plan_issues import create_plan_issue
|
|
41
|
+
from erk_shared.scratch.markers import PENDING_EXTRACTION_MARKER, delete_marker
|
|
42
|
+
from erk_shared.scratch.scratch import write_scratch_file
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@click.command(name="create-extraction-plan")
|
|
46
|
+
@click.option(
|
|
47
|
+
"--plan-file",
|
|
48
|
+
type=click.Path(exists=True, path_type=Path),
|
|
49
|
+
default=None,
|
|
50
|
+
help="Path to plan file to create issue from (use --plan-content instead)",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--plan-content",
|
|
54
|
+
type=str,
|
|
55
|
+
default=None,
|
|
56
|
+
help="Plan content to create issue from (preferred over --plan-file)",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--session-id",
|
|
60
|
+
type=str,
|
|
61
|
+
default=None,
|
|
62
|
+
help="Session ID for scratch directory (required with --plan-content)",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--source-plan-issues",
|
|
66
|
+
type=str,
|
|
67
|
+
default="",
|
|
68
|
+
help="Comma-separated list of source plan issue numbers (e.g., '123,456')",
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--extraction-session-ids",
|
|
72
|
+
type=str,
|
|
73
|
+
default="",
|
|
74
|
+
help="Comma-separated list of session IDs that were analyzed (e.g., 'abc123,def456')",
|
|
75
|
+
)
|
|
76
|
+
@click.pass_context
|
|
77
|
+
def create_extraction_plan(
|
|
78
|
+
ctx: click.Context,
|
|
79
|
+
plan_file: Path | None,
|
|
80
|
+
plan_content: str | None,
|
|
81
|
+
session_id: str | None,
|
|
82
|
+
source_plan_issues: str,
|
|
83
|
+
extraction_session_ids: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Create extraction plan issue from content or file with proper metadata.
|
|
86
|
+
|
|
87
|
+
Reads plan content from --plan-content or --plan-file and creates a GitHub issue with:
|
|
88
|
+
- erk-plan and erk-extraction labels
|
|
89
|
+
- plan_type: extraction in metadata
|
|
90
|
+
- Source tracking information
|
|
91
|
+
|
|
92
|
+
When using --plan-content, the content is automatically written to
|
|
93
|
+
.erk/scratch/<session-id>/extraction-plan.md
|
|
94
|
+
"""
|
|
95
|
+
# Get required context
|
|
96
|
+
github = require_github_issues(ctx)
|
|
97
|
+
repo_root = require_repo_root(ctx)
|
|
98
|
+
cwd = require_cwd(ctx)
|
|
99
|
+
|
|
100
|
+
# Validate options: must provide either --plan-content or --plan-file
|
|
101
|
+
if plan_content is None and plan_file is None:
|
|
102
|
+
click.echo(
|
|
103
|
+
json.dumps(
|
|
104
|
+
{
|
|
105
|
+
"success": False,
|
|
106
|
+
"error": "Must provide either --plan-content or --plan-file",
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
raise SystemExit(1)
|
|
111
|
+
|
|
112
|
+
if plan_content is not None and plan_file is not None:
|
|
113
|
+
click.echo(
|
|
114
|
+
json.dumps(
|
|
115
|
+
{
|
|
116
|
+
"success": False,
|
|
117
|
+
"error": "Cannot provide both --plan-content and --plan-file",
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
raise SystemExit(1)
|
|
122
|
+
|
|
123
|
+
# Handle --plan-content: requires --session-id, writes to scratch
|
|
124
|
+
if plan_content is not None:
|
|
125
|
+
if session_id is None:
|
|
126
|
+
click.echo(
|
|
127
|
+
json.dumps(
|
|
128
|
+
{
|
|
129
|
+
"success": False,
|
|
130
|
+
"error": "--session-id is required when using --plan-content",
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
raise SystemExit(1)
|
|
135
|
+
|
|
136
|
+
# Write to scratch directory
|
|
137
|
+
scratch_path = write_scratch_file(
|
|
138
|
+
plan_content,
|
|
139
|
+
session_id=session_id,
|
|
140
|
+
suffix=".md",
|
|
141
|
+
prefix="extraction-plan-",
|
|
142
|
+
repo_root=repo_root,
|
|
143
|
+
)
|
|
144
|
+
content = plan_content.strip()
|
|
145
|
+
else:
|
|
146
|
+
# Handle --plan-file: read content from file
|
|
147
|
+
# plan_file is guaranteed to be not None here
|
|
148
|
+
assert plan_file is not None # for type checker
|
|
149
|
+
content = plan_file.read_text(encoding="utf-8").strip()
|
|
150
|
+
scratch_path = None
|
|
151
|
+
|
|
152
|
+
if not content:
|
|
153
|
+
click.echo(json.dumps({"success": False, "error": "Empty plan content"}))
|
|
154
|
+
raise SystemExit(1)
|
|
155
|
+
|
|
156
|
+
# Parse source plan issues
|
|
157
|
+
source_issues: list[int] = []
|
|
158
|
+
if source_plan_issues:
|
|
159
|
+
for part in source_plan_issues.split(","):
|
|
160
|
+
part = part.strip()
|
|
161
|
+
if part:
|
|
162
|
+
try:
|
|
163
|
+
source_issues.append(int(part))
|
|
164
|
+
except ValueError as e:
|
|
165
|
+
click.echo(
|
|
166
|
+
json.dumps(
|
|
167
|
+
{
|
|
168
|
+
"success": False,
|
|
169
|
+
"error": f"Invalid issue number: {part}",
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
raise SystemExit(1) from e
|
|
174
|
+
|
|
175
|
+
# Parse session IDs
|
|
176
|
+
session_ids: list[str] = []
|
|
177
|
+
if extraction_session_ids:
|
|
178
|
+
for part in extraction_session_ids.split(","):
|
|
179
|
+
part = part.strip()
|
|
180
|
+
if part:
|
|
181
|
+
session_ids.append(part)
|
|
182
|
+
|
|
183
|
+
# Validate: at least one session ID must be provided
|
|
184
|
+
if not session_ids:
|
|
185
|
+
click.echo(
|
|
186
|
+
json.dumps(
|
|
187
|
+
{
|
|
188
|
+
"success": False,
|
|
189
|
+
"error": "At least one extraction_session_id is required",
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
raise SystemExit(1)
|
|
194
|
+
|
|
195
|
+
# Use consolidated create_plan_issue for the entire workflow
|
|
196
|
+
result = create_plan_issue(
|
|
197
|
+
github_issues=github,
|
|
198
|
+
repo_root=repo_root,
|
|
199
|
+
plan_content=content,
|
|
200
|
+
title=None,
|
|
201
|
+
plan_type="extraction",
|
|
202
|
+
extra_labels=None,
|
|
203
|
+
title_suffix=None,
|
|
204
|
+
source_plan_issues=source_issues if source_issues else None,
|
|
205
|
+
extraction_session_ids=session_ids,
|
|
206
|
+
source_repo=None,
|
|
207
|
+
objective_issue=None,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if not result.success:
|
|
211
|
+
if result.issue_number is not None:
|
|
212
|
+
# Partial success - issue created but comment failed
|
|
213
|
+
click.echo(
|
|
214
|
+
json.dumps(
|
|
215
|
+
{
|
|
216
|
+
"success": False,
|
|
217
|
+
"error": result.error,
|
|
218
|
+
"issue_number": result.issue_number,
|
|
219
|
+
"issue_url": result.issue_url,
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
else:
|
|
224
|
+
click.echo(json.dumps({"success": False, "error": result.error}))
|
|
225
|
+
raise SystemExit(1)
|
|
226
|
+
|
|
227
|
+
# Delete pending extraction marker since extraction is complete
|
|
228
|
+
delete_marker(cwd, PENDING_EXTRACTION_MARKER)
|
|
229
|
+
|
|
230
|
+
# Output success
|
|
231
|
+
output: dict[str, object] = {
|
|
232
|
+
"success": True,
|
|
233
|
+
"issue_number": result.issue_number,
|
|
234
|
+
"issue_url": result.issue_url,
|
|
235
|
+
"title": result.title,
|
|
236
|
+
"plan_type": "extraction",
|
|
237
|
+
"source_plan_issues": source_issues,
|
|
238
|
+
"extraction_session_ids": session_ids,
|
|
239
|
+
}
|
|
240
|
+
if scratch_path is not None:
|
|
241
|
+
output["scratch_path"] = str(scratch_path)
|
|
242
|
+
click.echo(json.dumps(output))
|