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,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Address PR review comments on current branch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /erk:pr-address
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
Fetches unresolved PR review comments AND PR discussion comments from the current branch's PR and addresses them using holistic analysis with smart batching. Comments are grouped by complexity and relationship, then processed batch-by-batch with incremental commits and resolution.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
/erk:pr-address
|
|
15
|
+
/erk:pr-address --all # Include resolved threads (for reference)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Agent Instructions
|
|
19
|
+
|
|
20
|
+
> **CRITICAL: Use ONLY `erk exec` Commands**
|
|
21
|
+
>
|
|
22
|
+
> - ❌ DO NOT use raw `gh api` calls
|
|
23
|
+
> - ❌ DO NOT use `gh pr` commands directly
|
|
24
|
+
> - ✅ ONLY use `erk exec get-pr-review-comments`, `erk exec resolve-review-thread`, etc.
|
|
25
|
+
>
|
|
26
|
+
> The `erk exec` commands handle thread resolution correctly. Raw API calls only reply without resolving.
|
|
27
|
+
|
|
28
|
+
### Phase 1: Fetch & Analyze
|
|
29
|
+
|
|
30
|
+
#### Step 1.1: Fetch All Comments
|
|
31
|
+
|
|
32
|
+
Run both CLI commands to get review comments AND discussion comments:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
erk exec get-pr-review-comments
|
|
36
|
+
erk exec get-pr-discussion-comments
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Review Comments JSON:**
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"success": true,
|
|
44
|
+
"pr_number": 123,
|
|
45
|
+
"pr_url": "https://github.com/owner/repo/pull/123",
|
|
46
|
+
"pr_title": "Feature: Add new capability",
|
|
47
|
+
"threads": [
|
|
48
|
+
{
|
|
49
|
+
"id": "PRRT_abc123",
|
|
50
|
+
"path": "src/foo.py",
|
|
51
|
+
"line": 42,
|
|
52
|
+
"is_outdated": false,
|
|
53
|
+
"comments": [
|
|
54
|
+
{
|
|
55
|
+
"author": "reviewer",
|
|
56
|
+
"body": "This should use LBYL pattern instead of try/except",
|
|
57
|
+
"created_at": "2024-01-01T10:00:00Z"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Discussion Comments JSON:**
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"success": true,
|
|
70
|
+
"pr_number": 123,
|
|
71
|
+
"pr_url": "https://github.com/owner/repo/pull/123",
|
|
72
|
+
"pr_title": "Feature: Add new capability",
|
|
73
|
+
"comments": [
|
|
74
|
+
{
|
|
75
|
+
"id": 12345,
|
|
76
|
+
"author": "reviewer",
|
|
77
|
+
"body": "Please also update the docs",
|
|
78
|
+
"url": "https://github.com/owner/repo/pull/123#issuecomment-12345"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Step 1.2: Handle No Comments Case
|
|
85
|
+
|
|
86
|
+
If both `threads` is empty AND `comments` is empty, display: "No unresolved review comments or discussion comments on PR #123."
|
|
87
|
+
|
|
88
|
+
#### Step 1.3: Holistic Analysis
|
|
89
|
+
|
|
90
|
+
Analyze ALL comments together to understand relationships and complexity. Classify each comment:
|
|
91
|
+
|
|
92
|
+
- **Local fix**: Single comment → single location change (e.g., "Fix typo", "Add type annotation")
|
|
93
|
+
- **Multi-location in same file**: Single comment → changes in multiple spots in one file
|
|
94
|
+
- **Cross-cutting**: Single comment → changes across multiple files
|
|
95
|
+
- **Related comments**: Multiple comments that inform a single unified change (e.g., two comments about the same refactor)
|
|
96
|
+
|
|
97
|
+
#### Step 1.4: Batch and Prioritize
|
|
98
|
+
|
|
99
|
+
Group comments into batches ordered by complexity (simplest first):
|
|
100
|
+
|
|
101
|
+
| Batch | Complexity | Description | Example |
|
|
102
|
+
| ----- | -------------------------- | ----------------------------------- | --------------------------------------------------------- |
|
|
103
|
+
| 1 | Local fixes | One file, one location per comment | "Use LBYL pattern at line 42" |
|
|
104
|
+
| 2 | Single-file multi-location | One file, multiple locations | "Rename this variable everywhere in this file" |
|
|
105
|
+
| 3 | Cross-cutting | Multiple files affected | "Update all callers of this function" |
|
|
106
|
+
| 4 | Complex/Related | Multiple comments inform one change | "Fold validate into prepare" + "Use union types for this" |
|
|
107
|
+
|
|
108
|
+
**Note**: Discussion comments that require doc updates or non-code changes go in Batch 3 (cross-cutting) since they often affect multiple files.
|
|
109
|
+
|
|
110
|
+
### Phase 2: Display Batched Plan
|
|
111
|
+
|
|
112
|
+
Show the user the batched execution plan:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
## Execution Plan
|
|
116
|
+
|
|
117
|
+
### Batch 1: Local Fixes (3 comments)
|
|
118
|
+
| # | Location | Summary |
|
|
119
|
+
|---|----------|---------|
|
|
120
|
+
| 1 | foo.py:42 | Use LBYL pattern |
|
|
121
|
+
| 2 | bar.py:15 | Add type annotation |
|
|
122
|
+
| 3 | baz.py:99 | Fix typo |
|
|
123
|
+
|
|
124
|
+
### Batch 2: Single-File Changes (1 comment)
|
|
125
|
+
| # | Location | Summary |
|
|
126
|
+
|---|----------|---------|
|
|
127
|
+
| 4 | impl.py (multiple) | Rename `old_name` to `new_name` throughout |
|
|
128
|
+
|
|
129
|
+
### Batch 3: Cross-Cutting Changes (2 comments)
|
|
130
|
+
| # | Location | Summary |
|
|
131
|
+
|---|----------|---------|
|
|
132
|
+
| 5 | Multiple files | Update all callers of deprecated function |
|
|
133
|
+
| 6 | docs/ | Update documentation per reviewer request |
|
|
134
|
+
|
|
135
|
+
### Batch 4: Complex Changes (2 comments → 1 unified change)
|
|
136
|
+
| # | Location | Summary |
|
|
137
|
+
|---|----------|---------|
|
|
138
|
+
| 7 | impl.py:50 | Fold validate into prepare with union types |
|
|
139
|
+
| 8 | cmd.py:100 | (related to #7 - same refactor) |
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**User confirmation flow:**
|
|
143
|
+
|
|
144
|
+
- **Batch 1-2 (simple)**: Auto-proceed without confirmation
|
|
145
|
+
- **Batch 3-4 (complex)**: Show plan and wait for user approval before executing
|
|
146
|
+
|
|
147
|
+
### Phase 3: Execute by Batch
|
|
148
|
+
|
|
149
|
+
For each batch, execute this workflow:
|
|
150
|
+
|
|
151
|
+
#### Step 3.1: Address All Comments in the Batch
|
|
152
|
+
|
|
153
|
+
For each comment in the batch:
|
|
154
|
+
|
|
155
|
+
**For Review Threads:**
|
|
156
|
+
|
|
157
|
+
1. Read the file to understand context:
|
|
158
|
+
- If `line` is specified: Read around that line number
|
|
159
|
+
- If `line` is null (outdated thread): Read the entire file or search for relevant code mentioned in the comment
|
|
160
|
+
2. Make the fix following the reviewer's feedback
|
|
161
|
+
3. Track the change for the batch commit message
|
|
162
|
+
|
|
163
|
+
**For Discussion Comments:**
|
|
164
|
+
|
|
165
|
+
1. Determine if action is needed:
|
|
166
|
+
- If it's a request (e.g., "Please update docs"), take the requested action
|
|
167
|
+
- If it's a question, provide an answer or make clarifying changes
|
|
168
|
+
- If it's architectural feedback/suggestion, investigate the codebase to understand implications
|
|
169
|
+
- If it's just acknowledgment/thanks, note it and move on
|
|
170
|
+
2. **Investigate the codebase** when the comment requires understanding existing code:
|
|
171
|
+
- Search for relevant patterns, existing implementations, or related code
|
|
172
|
+
- Note any interesting findings that inform your decision
|
|
173
|
+
- Record these findings - they become permanent documentation in the reply
|
|
174
|
+
3. Take action if needed
|
|
175
|
+
|
|
176
|
+
**Handling False Positives from Automated Reviewers:**
|
|
177
|
+
|
|
178
|
+
Automated review bots (like `dignified-python-review`, linters, or security scanners) can flag false positives. Before making code changes:
|
|
179
|
+
|
|
180
|
+
1. **Read the flagged code carefully** - understand what the bot is complaining about
|
|
181
|
+
2. **Verify if it's a false positive** by checking:
|
|
182
|
+
- Is the pattern the bot wants already implemented nearby? (e.g., LBYL check already exists on a preceding line)
|
|
183
|
+
- Is the bot misunderstanding the code structure?
|
|
184
|
+
- Is the bot applying a rule that doesn't fit this specific context?
|
|
185
|
+
3. **If it's a false positive**, do NOT make unnecessary code changes. Instead:
|
|
186
|
+
- Reply to the comment explaining why it's a false positive
|
|
187
|
+
- Reference specific line numbers where the correct pattern already exists
|
|
188
|
+
- Resolve the thread
|
|
189
|
+
|
|
190
|
+
**Example reply for a false positive:**
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
erk exec resolve-review-thread --thread-id "PRRT_abc123" --comment "False positive: The LBYL check the bot is requesting already exists on line 344 where we check \`.exists()\` before the operation on line 353. No code change needed."
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**For Outdated Review Threads** (`is_outdated: true`):
|
|
197
|
+
|
|
198
|
+
Outdated threads have `line: null` because the code has changed since the comment was made.
|
|
199
|
+
|
|
200
|
+
1. **Read the file** at the path (ignore line number - search for relevant code)
|
|
201
|
+
2. **Check if the issue is already fixed** in the current code
|
|
202
|
+
3. **Take action:**
|
|
203
|
+
- If already fixed → Proceed directly to Step 3.4 to resolve the thread
|
|
204
|
+
- If not fixed → Apply the fix, then proceed to Step 3.4
|
|
205
|
+
|
|
206
|
+
**IMPORTANT**: Outdated threads MUST still be resolved via `erk exec resolve-review-thread`.
|
|
207
|
+
Do not skip resolution just because no code change was needed.
|
|
208
|
+
|
|
209
|
+
#### Step 3.2: Run CI Checks
|
|
210
|
+
|
|
211
|
+
After making all changes in the batch:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Run relevant CI checks for changed files
|
|
215
|
+
# (This may vary by project - use project's test commands)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If CI fails, fix the issues before proceeding.
|
|
219
|
+
|
|
220
|
+
#### Step 3.3: Commit the Batch
|
|
221
|
+
|
|
222
|
+
Create a single commit for all changes in the batch:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git add <changed files>
|
|
226
|
+
git commit -m "Address PR review comments (batch N/M)
|
|
227
|
+
|
|
228
|
+
- <summary of comment 1>
|
|
229
|
+
- <summary of comment 2>
|
|
230
|
+
..."
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Step 3.4: Resolve All Threads in the Batch (MANDATORY)
|
|
234
|
+
|
|
235
|
+
**This step is NOT optional.** Every thread must be resolved using `erk exec resolve-review-thread`.
|
|
236
|
+
|
|
237
|
+
> **IMPORTANT: Replying ≠ Resolving**
|
|
238
|
+
>
|
|
239
|
+
> - **Replying** (via raw `gh api .../replies`): Adds a comment but thread stays OPEN
|
|
240
|
+
> - **Resolving** (via `erk exec resolve-review-thread`): Adds a comment AND marks thread as RESOLVED
|
|
241
|
+
>
|
|
242
|
+
> Always use `erk exec resolve-review-thread` - it does both in one operation.
|
|
243
|
+
|
|
244
|
+
After committing, resolve each review thread and mark each discussion comment:
|
|
245
|
+
|
|
246
|
+
**For Review Threads:**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
erk exec resolve-review-thread --thread-id "PRRT_abc123" --comment "Resolved via /erk:pr-address at $(date '+%Y-%m-%d %I:%M %p %Z')"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Resolving already-fixed outdated threads:**
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
erk exec resolve-review-thread --thread-id "PRRT_abc123" --comment "Already addressed in current code - this outdated thread can be resolved."
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**For Discussion Comments:**
|
|
259
|
+
|
|
260
|
+
Post a substantive reply that quotes the original comment and explains what action was taken:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
erk exec reply-to-discussion-comment --comment-id 12345 --reply "**Action taken:** <substantive summary>"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Writing substantive replies:**
|
|
267
|
+
|
|
268
|
+
The `--reply` argument should include meaningful findings, not just generic acknowledgments:
|
|
269
|
+
|
|
270
|
+
❌ **Bad (too generic):**
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
--reply "**Action taken:** Noted for future consideration."
|
|
274
|
+
--reply "**Action taken:** Added to backlog."
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
✅ **Good (includes investigation findings):**
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
--reply "**Action taken:** Investigated the gateway pattern suggestion. The current artifact sync implementation uses direct function calls rather than a gateway ABC pattern. This is intentional - artifact operations are file-based and don't require the testability benefits of gateway injection that external APIs need. Filed as backlog consideration for if we add remote artifact fetching."
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
✅ **Good (explains why no code change):**
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
--reply "**Action taken:** Reviewed the suggestion to add caching here. After checking the call sites, this function is only called once per CLI invocation (in main.py:45), so caching wouldn't provide measurable benefit. The perceived slowness is actually from the subprocess call inside, not repeated invocations."
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The reply becomes a permanent record in the PR - make it useful for future readers who wonder "what happened with this feedback?"
|
|
290
|
+
|
|
291
|
+
#### Step 3.5: Report Progress
|
|
292
|
+
|
|
293
|
+
After completing the batch, report:
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
## Batch N Complete
|
|
297
|
+
|
|
298
|
+
Addressed:
|
|
299
|
+
- ✅ foo.py:42 - Used LBYL pattern
|
|
300
|
+
- ✅ bar.py:15 - Added type annotation
|
|
301
|
+
|
|
302
|
+
Committed: abc1234 "Address PR review comments (batch 1/3)"
|
|
303
|
+
|
|
304
|
+
Resolved threads: 2
|
|
305
|
+
Remaining batches: 2
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Then proceed to the next batch.
|
|
309
|
+
|
|
310
|
+
### Phase 4: Final Verification
|
|
311
|
+
|
|
312
|
+
After all batches complete:
|
|
313
|
+
|
|
314
|
+
#### Step 4.1: Verify All Threads Resolved
|
|
315
|
+
|
|
316
|
+
Re-fetch comments to confirm nothing was missed:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
erk exec get-pr-review-comments
|
|
320
|
+
erk exec get-pr-discussion-comments
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
If any unresolved threads remain, report them.
|
|
324
|
+
|
|
325
|
+
#### Step 4.2: Report Summary
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
## All PR Comments Addressed
|
|
329
|
+
|
|
330
|
+
Total comments: 8
|
|
331
|
+
Batches: 4
|
|
332
|
+
Commits: 4
|
|
333
|
+
|
|
334
|
+
All review threads resolved.
|
|
335
|
+
All discussion comments marked with reaction.
|
|
336
|
+
|
|
337
|
+
Next steps:
|
|
338
|
+
1. Push changes: `git push`
|
|
339
|
+
2. Wait for CI to pass
|
|
340
|
+
3. Request re-review if needed
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### Step 4.3: Handle Any Skipped Comments
|
|
344
|
+
|
|
345
|
+
If the user explicitly skipped any comments during the process, list them:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
## Skipped Comments (user choice)
|
|
349
|
+
- #5: src/legacy.py:100 - "Refactor this module" (user deferred)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Common Mistakes to Avoid
|
|
353
|
+
|
|
354
|
+
| Mistake | Why It's Wrong | Correct Approach |
|
|
355
|
+
| ---------------------------------------------- | --------------------------------- | ------------------------------------- |
|
|
356
|
+
| Using `gh api repos/.../comments/{id}/replies` | Only replies, doesn't resolve | Use `erk exec resolve-review-thread` |
|
|
357
|
+
| Using `gh pr comment` | Doesn't resolve threads | Use `erk exec resolve-review-thread` |
|
|
358
|
+
| Skipping resolution for outdated threads | Threads stay open in PR | Always resolve, even if already fixed |
|
|
359
|
+
| Not re-fetching after resolution | Can't verify all threads resolved | Always run Step 4.1 verification |
|
|
360
|
+
|
|
361
|
+
### Error Handling
|
|
362
|
+
|
|
363
|
+
**No PR for branch:** Display error and suggest creating a PR with `gt create` or `gh pr create`
|
|
364
|
+
|
|
365
|
+
**GitHub API error:** Display error and suggest checking `gh auth status` and repository access
|
|
366
|
+
|
|
367
|
+
**CI failure during batch:** Stop, display the failure, and let the user decide whether to fix and continue or abort
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
Create git commit and submit current branch with Graphite (squashes commits
|
|
4
|
+
and rebases stack)
|
|
5
|
+
argument-hint: <description>
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Submit PR
|
|
9
|
+
|
|
10
|
+
Automatically create a git commit with a helpful summary message and submit the current branch as a pull request.
|
|
11
|
+
|
|
12
|
+
**Note:** This command squashes commits and rebases the stack. If you prefer a simpler workflow that preserves your commit history, use `/erk:git-pr-push` instead.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Invoke the command (description argument is optional but recommended)
|
|
18
|
+
/erk:pr-submit "Add user authentication feature"
|
|
19
|
+
|
|
20
|
+
# Without argument (will analyze changes automatically)
|
|
21
|
+
/erk:pr-submit
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Implementation
|
|
25
|
+
|
|
26
|
+
This command delegates to `erk pr submit` which handles everything:
|
|
27
|
+
|
|
28
|
+
- Preflight checks (auth, squash commits, submit to Graphite)
|
|
29
|
+
- AI-powered commit message generation
|
|
30
|
+
- PR metadata update with title and body
|
|
31
|
+
- PR validation
|
|
32
|
+
|
|
33
|
+
### Execute
|
|
34
|
+
|
|
35
|
+
Run the full submit workflow:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
erk pr submit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Report Results
|
|
42
|
+
|
|
43
|
+
The command outputs:
|
|
44
|
+
|
|
45
|
+
- PR URL
|
|
46
|
+
- Graphite URL
|
|
47
|
+
- Success message
|
|
48
|
+
|
|
49
|
+
## Error Handling
|
|
50
|
+
|
|
51
|
+
If `erk pr submit` fails, display the error and stop. The Python implementation handles all error cases including:
|
|
52
|
+
|
|
53
|
+
- Authentication issues (Graphite/GitHub)
|
|
54
|
+
- Merge conflicts
|
|
55
|
+
- No commits to submit
|
|
56
|
+
- Submission failures
|
|
57
|
+
|
|
58
|
+
Do NOT attempt to auto-resolve errors. Let the user fix issues and re-run.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dignified-python
|
|
3
|
+
description: Python coding standards with automatic version detection.
|
|
4
|
+
Use when writing, reviewing, or refactoring Python to ensure adherence to LBYL exception
|
|
5
|
+
handling patterns, modern type syntax (list[str], str | None), pathlib operations,
|
|
6
|
+
ABC-based interfaces, absolute imports, and explicit error boundaries at CLI level.
|
|
7
|
+
Also provides production-tested code smell patterns from Dagster Labs for API design,
|
|
8
|
+
parameter complexity, and code organization. Essential for maintaining erk's dignified
|
|
9
|
+
Python standards.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Dignified Python Coding Standards
|
|
13
|
+
|
|
14
|
+
## Core Knowledge (ALWAYS Loaded)
|
|
15
|
+
|
|
16
|
+
@dignified-python-core.md
|
|
17
|
+
|
|
18
|
+
## Version Detection
|
|
19
|
+
|
|
20
|
+
**Identify the project's minimum Python version** by checking (in order):
|
|
21
|
+
|
|
22
|
+
1. `pyproject.toml` - Look for `requires-python` field (e.g., `requires-python = ">=3.12"`)
|
|
23
|
+
2. `setup.py` or `setup.cfg` - Look for `python_requires`
|
|
24
|
+
3. `.python-version` file - Contains version like `3.12` or `3.12.0`
|
|
25
|
+
4. Default to Python 3.12 if no version specifier found
|
|
26
|
+
|
|
27
|
+
**Once identified, load the appropriate version-specific file:**
|
|
28
|
+
|
|
29
|
+
- Python 3.10: Load `versions/python-3.10.md`
|
|
30
|
+
- Python 3.11: Load `versions/python-3.11.md`
|
|
31
|
+
- Python 3.12: Load `versions/python-3.12.md`
|
|
32
|
+
- Python 3.13: Load `versions/python-3.13.md`
|
|
33
|
+
|
|
34
|
+
## Conditional Loading (Load Based on Task Patterns)
|
|
35
|
+
|
|
36
|
+
Core files above cover 80%+ of Python code patterns. Only load these additional files when you detect specific patterns:
|
|
37
|
+
|
|
38
|
+
Pattern detection examples:
|
|
39
|
+
|
|
40
|
+
- If task mentions "click" or "CLI" -> Load `cli-patterns.md`
|
|
41
|
+
- If task mentions "subprocess" -> Load `subprocess.md`
|
|
42
|
+
|
|
43
|
+
## How to Use This Skill
|
|
44
|
+
|
|
45
|
+
1. **Core knowledge** is loaded automatically (LBYL, pathlib, ABC, imports, exceptions)
|
|
46
|
+
2. **Version detection** happens once - identify the minimum Python version and load the appropriate version file
|
|
47
|
+
3. **Additional patterns** may require extra loading (CLI patterns, subprocess)
|
|
48
|
+
4. **Each file is self-contained** with complete guidance for its domain
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# CLI Patterns - Click Best Practices
|
|
5
|
+
|
|
6
|
+
## Core Rules
|
|
7
|
+
|
|
8
|
+
1. **Use `click.echo()` for output, NEVER `print()`**
|
|
9
|
+
2. **Exit with `raise SystemExit(1)` for CLI errors**
|
|
10
|
+
3. **Error boundaries at command level**
|
|
11
|
+
4. **Use `err=True` for error output**
|
|
12
|
+
5. **Use `user_confirm()` for confirmation prompts** (handles stderr flushing)
|
|
13
|
+
|
|
14
|
+
## Basic Click Patterns
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import click
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
# ✅ CORRECT: Use click.echo for output
|
|
21
|
+
@click.command()
|
|
22
|
+
@click.argument("name")
|
|
23
|
+
def greet(name: str) -> None:
|
|
24
|
+
"""Greet the user."""
|
|
25
|
+
click.echo(f"Hello, {name}!")
|
|
26
|
+
|
|
27
|
+
# ❌ WRONG: Using print()
|
|
28
|
+
@click.command()
|
|
29
|
+
def greet(name: str) -> None:
|
|
30
|
+
print(f"Hello, {name}!") # NEVER use print in CLI
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Error Handling in CLI
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
# ✅ CORRECT: CLI command error boundary
|
|
37
|
+
@click.command("create")
|
|
38
|
+
@click.pass_obj
|
|
39
|
+
def create(ctx: ErkContext, name: str) -> None:
|
|
40
|
+
"""Create a worktree."""
|
|
41
|
+
try:
|
|
42
|
+
create_worktree(ctx, name)
|
|
43
|
+
except subprocess.CalledProcessError as e:
|
|
44
|
+
click.echo(f"Error: Git command failed: {e.stderr}", err=True)
|
|
45
|
+
raise SystemExit(1)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
click.echo(f"Error: {e}", err=True)
|
|
48
|
+
raise SystemExit(1)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Output Patterns
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Regular output to stdout
|
|
55
|
+
click.echo("Processing complete")
|
|
56
|
+
|
|
57
|
+
# Error output to stderr
|
|
58
|
+
click.echo("Error: Operation failed", err=True)
|
|
59
|
+
|
|
60
|
+
# Colored output
|
|
61
|
+
click.echo(click.style("Success!", fg="green"))
|
|
62
|
+
click.echo(click.style("Warning!", fg="yellow", bold=True))
|
|
63
|
+
|
|
64
|
+
# Progress indication
|
|
65
|
+
with click.progressbar(items) as bar:
|
|
66
|
+
for item in bar:
|
|
67
|
+
process(item)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Command Structure
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
@click.group()
|
|
74
|
+
@click.pass_context
|
|
75
|
+
def cli(ctx: click.Context) -> None:
|
|
76
|
+
"""Main CLI entry point."""
|
|
77
|
+
ctx.ensure_object(dict)
|
|
78
|
+
ctx.obj["config"] = load_config()
|
|
79
|
+
|
|
80
|
+
@cli.command()
|
|
81
|
+
@click.option("--dry-run", is_flag=True, help="Perform dry run")
|
|
82
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
83
|
+
@click.pass_obj
|
|
84
|
+
def sync(obj: dict, path: str, dry_run: bool) -> None:
|
|
85
|
+
"""Sync the repository."""
|
|
86
|
+
config = obj["config"]
|
|
87
|
+
|
|
88
|
+
if dry_run:
|
|
89
|
+
click.echo("DRY RUN: Would sync...")
|
|
90
|
+
else:
|
|
91
|
+
perform_sync(Path(path), config)
|
|
92
|
+
click.echo("✓ Sync complete")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## User Interaction
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from erk_shared.output import user_output, user_confirm
|
|
99
|
+
|
|
100
|
+
# ✅ CORRECT: Use user_confirm() for confirmation prompts
|
|
101
|
+
# This ensures stderr is flushed before prompting
|
|
102
|
+
user_output("Warning: This operation is destructive!")
|
|
103
|
+
if user_confirm("Are you sure?"):
|
|
104
|
+
perform_dangerous_operation()
|
|
105
|
+
|
|
106
|
+
# ❌ WRONG: Direct click.confirm() after user_output()
|
|
107
|
+
# This can hang because stderr isn't flushed before the prompt
|
|
108
|
+
user_output("Warning: This operation is destructive!")
|
|
109
|
+
if click.confirm("Are you sure?"): # BAD: buffering hang
|
|
110
|
+
perform_dangerous_operation()
|
|
111
|
+
|
|
112
|
+
# User input (for non-boolean prompts, use click.prompt directly)
|
|
113
|
+
name = click.prompt("Enter your name", default="User")
|
|
114
|
+
|
|
115
|
+
# Password input
|
|
116
|
+
password = click.prompt("Password", hide_input=True)
|
|
117
|
+
|
|
118
|
+
# Choice selection
|
|
119
|
+
choice = click.prompt(
|
|
120
|
+
"Select option",
|
|
121
|
+
type=click.Choice(["option1", "option2"]),
|
|
122
|
+
default="option1"
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Path Handling
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
@click.command()
|
|
130
|
+
@click.argument(
|
|
131
|
+
"input_file",
|
|
132
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False)
|
|
133
|
+
)
|
|
134
|
+
@click.argument(
|
|
135
|
+
"output_dir",
|
|
136
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True)
|
|
137
|
+
)
|
|
138
|
+
def process(input_file: str, output_dir: str) -> None:
|
|
139
|
+
"""Process input file to output directory."""
|
|
140
|
+
input_path = Path(input_file)
|
|
141
|
+
output_path = Path(output_dir)
|
|
142
|
+
|
|
143
|
+
if not output_path.exists():
|
|
144
|
+
output_path.mkdir(parents=True)
|
|
145
|
+
|
|
146
|
+
click.echo(f"Processing {input_path} → {output_path}")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Key Takeaways
|
|
150
|
+
|
|
151
|
+
1. **Always click.echo()**: Never use print() in CLI code
|
|
152
|
+
2. **Error to stderr**: Use `err=True` for error messages
|
|
153
|
+
3. **Exit cleanly**: Use `raise SystemExit(1)` for errors
|
|
154
|
+
4. **User-friendly**: Provide clear messages and confirmations
|
|
155
|
+
5. **Type paths**: Use `click.Path()` for path arguments
|