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,76 @@
|
|
|
1
|
+
"""Git status collector."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from erk.core.context import ErkContext
|
|
6
|
+
from erk.status.collectors.base import StatusCollector
|
|
7
|
+
from erk.status.models.status_data import CommitInfo, GitStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GitStatusCollector(StatusCollector):
|
|
11
|
+
"""Collects git repository status information."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
"""Name identifier for this collector."""
|
|
16
|
+
return "git"
|
|
17
|
+
|
|
18
|
+
def is_available(self, ctx: ErkContext, worktree_path: Path) -> bool:
|
|
19
|
+
"""Check if git operations are available.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
ctx: Erk context
|
|
23
|
+
worktree_path: Path to worktree
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
True if worktree exists and has git
|
|
27
|
+
"""
|
|
28
|
+
if not worktree_path.exists():
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
def collect(self, ctx: ErkContext, worktree_path: Path, repo_root: Path) -> GitStatus | None:
|
|
34
|
+
"""Collect git status information.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
ctx: Erk context
|
|
38
|
+
worktree_path: Path to worktree
|
|
39
|
+
repo_root: Repository root path
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
GitStatus with repository information or None if collection fails
|
|
43
|
+
"""
|
|
44
|
+
branch = ctx.git.get_current_branch(worktree_path)
|
|
45
|
+
if branch is None:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Get git status
|
|
49
|
+
staged, modified, untracked = ctx.git.get_file_status(worktree_path)
|
|
50
|
+
clean = len(staged) == 0 and len(modified) == 0 and len(untracked) == 0
|
|
51
|
+
|
|
52
|
+
# Get ahead/behind counts
|
|
53
|
+
ahead, behind = ctx.git.get_ahead_behind(worktree_path, branch)
|
|
54
|
+
|
|
55
|
+
# Get recent commits
|
|
56
|
+
commit_dicts = ctx.git.get_recent_commits(worktree_path, limit=5)
|
|
57
|
+
recent_commits = [
|
|
58
|
+
CommitInfo(
|
|
59
|
+
sha=c["sha"],
|
|
60
|
+
message=c["message"],
|
|
61
|
+
author=c["author"],
|
|
62
|
+
date=c["date"],
|
|
63
|
+
)
|
|
64
|
+
for c in commit_dicts
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return GitStatus(
|
|
68
|
+
branch=branch,
|
|
69
|
+
clean=clean,
|
|
70
|
+
ahead=ahead,
|
|
71
|
+
behind=behind,
|
|
72
|
+
staged_files=staged,
|
|
73
|
+
modified_files=modified,
|
|
74
|
+
untracked_files=untracked,
|
|
75
|
+
recent_commits=recent_commits,
|
|
76
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""GitHub PR collector."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from erk.core.context import ErkContext
|
|
6
|
+
from erk.status.collectors.base import StatusCollector
|
|
7
|
+
from erk.status.models.status_data import PullRequestStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GitHubPRCollector(StatusCollector):
|
|
11
|
+
"""Collects GitHub pull request information."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
"""Name identifier for this collector."""
|
|
16
|
+
return "pr"
|
|
17
|
+
|
|
18
|
+
def is_available(self, ctx: ErkContext, worktree_path: Path) -> bool:
|
|
19
|
+
"""Check if PR information should be fetched.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
ctx: Erk context
|
|
23
|
+
worktree_path: Path to worktree
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
True if PR info is enabled in config
|
|
27
|
+
"""
|
|
28
|
+
if not (ctx.global_config and ctx.global_config.show_pr_info):
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
if not worktree_path.exists():
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def collect(
|
|
37
|
+
self, ctx: ErkContext, worktree_path: Path, repo_root: Path
|
|
38
|
+
) -> PullRequestStatus | None:
|
|
39
|
+
"""Collect GitHub PR information.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
ctx: Erk context
|
|
43
|
+
worktree_path: Path to worktree
|
|
44
|
+
repo_root: Repository root path
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
PullRequestStatus with PR information or None if collection fails
|
|
48
|
+
"""
|
|
49
|
+
branch = ctx.git.get_current_branch(worktree_path)
|
|
50
|
+
if branch is None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Always use Graphite (fast, no pagination issues)
|
|
54
|
+
prs = ctx.graphite.get_prs_from_graphite(ctx.git, repo_root)
|
|
55
|
+
|
|
56
|
+
# Fail fast if Graphite cache unavailable - no fallback to GitHub
|
|
57
|
+
if not prs:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
# Find PR for current branch
|
|
61
|
+
pr = prs.get(branch)
|
|
62
|
+
if pr is None:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Determine if ready to merge
|
|
66
|
+
ready_to_merge = (
|
|
67
|
+
pr.state == "OPEN"
|
|
68
|
+
and not pr.is_draft
|
|
69
|
+
and (pr.checks_passing is True or pr.checks_passing is None)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return PullRequestStatus(
|
|
73
|
+
number=pr.number,
|
|
74
|
+
title=None, # Title not available in PullRequestInfo
|
|
75
|
+
state=pr.state,
|
|
76
|
+
is_draft=pr.is_draft,
|
|
77
|
+
url=pr.url,
|
|
78
|
+
checks_passing=pr.checks_passing,
|
|
79
|
+
reviews=None, # Reviews not available in PullRequestInfo
|
|
80
|
+
ready_to_merge=ready_to_merge,
|
|
81
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Worktree stack collector."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from erk.core.context import ErkContext
|
|
6
|
+
from erk.status.collectors.base import StatusCollector
|
|
7
|
+
from erk.status.models.status_data import StackPosition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GraphiteStackCollector(StatusCollector):
|
|
11
|
+
"""Collects worktree stack position information."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
"""Name identifier for this collector."""
|
|
16
|
+
return "stack"
|
|
17
|
+
|
|
18
|
+
def is_available(self, ctx: ErkContext, worktree_path: Path) -> bool:
|
|
19
|
+
"""Check if Graphite is enabled and available.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
ctx: Erk context
|
|
23
|
+
worktree_path: Path to worktree
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
True if Graphite is enabled
|
|
27
|
+
"""
|
|
28
|
+
if not (ctx.global_config and ctx.global_config.use_graphite):
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
if not worktree_path.exists():
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def collect(
|
|
37
|
+
self, ctx: ErkContext, worktree_path: Path, repo_root: Path
|
|
38
|
+
) -> StackPosition | None:
|
|
39
|
+
"""Collect worktree stack information.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
ctx: Erk context
|
|
43
|
+
worktree_path: Path to worktree
|
|
44
|
+
repo_root: Repository root path
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
StackPosition with stack information or None if collection fails
|
|
48
|
+
"""
|
|
49
|
+
branch = ctx.git.get_current_branch(worktree_path)
|
|
50
|
+
if branch is None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Get the stack for current branch
|
|
54
|
+
stack = ctx.graphite.get_branch_stack(ctx.git, repo_root, branch)
|
|
55
|
+
if stack is None:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# Find current branch position in stack
|
|
59
|
+
if branch not in stack:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
current_idx = stack.index(branch)
|
|
63
|
+
|
|
64
|
+
# Determine parent and children
|
|
65
|
+
parent_branch = stack[current_idx - 1] if current_idx > 0 else None
|
|
66
|
+
|
|
67
|
+
children_branches = []
|
|
68
|
+
if current_idx < len(stack) - 1:
|
|
69
|
+
children_branches.append(stack[current_idx + 1])
|
|
70
|
+
|
|
71
|
+
# Check if this is trunk
|
|
72
|
+
is_trunk = current_idx == 0
|
|
73
|
+
|
|
74
|
+
return StackPosition(
|
|
75
|
+
stack=stack,
|
|
76
|
+
current_branch=branch,
|
|
77
|
+
parent_branch=parent_branch,
|
|
78
|
+
children_branches=children_branches,
|
|
79
|
+
is_trunk=is_trunk,
|
|
80
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Implementation folder collector."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import frontmatter
|
|
7
|
+
|
|
8
|
+
from erk.core.context import ErkContext
|
|
9
|
+
from erk.status.collectors.base import StatusCollector
|
|
10
|
+
from erk.status.models.status_data import PlanStatus
|
|
11
|
+
from erk_shared.impl_folder import (
|
|
12
|
+
get_impl_path,
|
|
13
|
+
read_issue_reference,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def detect_enriched_plan(repo_root: Path) -> tuple[Path | None, str | None]:
|
|
20
|
+
"""Detect enriched plan file at repository root.
|
|
21
|
+
|
|
22
|
+
Scans for *-plan.md files and checks for erk_plan marker.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
repo_root: Repository root path
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Tuple of (path, filename) or (None, None) if not found
|
|
29
|
+
"""
|
|
30
|
+
if not repo_root.exists():
|
|
31
|
+
return None, None
|
|
32
|
+
|
|
33
|
+
# Find all *-plan.md files
|
|
34
|
+
plan_files = list(repo_root.glob("*-plan.md"))
|
|
35
|
+
|
|
36
|
+
if not plan_files:
|
|
37
|
+
return None, None
|
|
38
|
+
|
|
39
|
+
# Sort by modification time (most recent first)
|
|
40
|
+
plan_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
41
|
+
|
|
42
|
+
# Check each file for enrichment marker
|
|
43
|
+
for plan_file in plan_files:
|
|
44
|
+
# Use frontmatter library to parse YAML frontmatter
|
|
45
|
+
post = frontmatter.load(str(plan_file))
|
|
46
|
+
|
|
47
|
+
# Check for enrichment marker (handles missing frontmatter gracefully)
|
|
48
|
+
if post.get("erk_plan") is True:
|
|
49
|
+
return plan_file, plan_file.name
|
|
50
|
+
|
|
51
|
+
return None, None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PlanFileCollector(StatusCollector):
|
|
55
|
+
"""Collects information about .impl/ folder."""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
"""Name identifier for this collector."""
|
|
60
|
+
return "plan"
|
|
61
|
+
|
|
62
|
+
def is_available(self, ctx: ErkContext, worktree_path: Path) -> bool:
|
|
63
|
+
"""Check if .impl/plan.md exists.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
ctx: Erk context
|
|
67
|
+
worktree_path: Path to worktree
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if .impl/plan.md exists
|
|
71
|
+
"""
|
|
72
|
+
impl_path = get_impl_path(worktree_path, git_ops=ctx.git)
|
|
73
|
+
return impl_path is not None
|
|
74
|
+
|
|
75
|
+
def collect(self, ctx: ErkContext, worktree_path: Path, repo_root: Path) -> PlanStatus | None:
|
|
76
|
+
"""Collect implementation folder information.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
ctx: Erk context
|
|
80
|
+
worktree_path: Path to worktree
|
|
81
|
+
repo_root: Repository root path
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
PlanStatus with folder information or None if collection fails
|
|
85
|
+
"""
|
|
86
|
+
impl_path = get_impl_path(worktree_path, git_ops=ctx.git)
|
|
87
|
+
|
|
88
|
+
# Detect enriched plan at repo root
|
|
89
|
+
enriched_plan_path, enriched_plan_filename = detect_enriched_plan(repo_root)
|
|
90
|
+
|
|
91
|
+
if impl_path is None:
|
|
92
|
+
return PlanStatus(
|
|
93
|
+
exists=False,
|
|
94
|
+
path=None,
|
|
95
|
+
summary=None,
|
|
96
|
+
line_count=0,
|
|
97
|
+
first_lines=[],
|
|
98
|
+
format="none",
|
|
99
|
+
enriched_plan_path=enriched_plan_path,
|
|
100
|
+
enriched_plan_filename=enriched_plan_filename,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Read plan.md
|
|
104
|
+
content = impl_path.read_text(encoding="utf-8")
|
|
105
|
+
lines = content.splitlines()
|
|
106
|
+
line_count = len(lines)
|
|
107
|
+
|
|
108
|
+
# Get first 5 lines
|
|
109
|
+
first_lines = lines[:5] if len(lines) >= 5 else lines
|
|
110
|
+
|
|
111
|
+
# Extract summary from first few non-empty lines
|
|
112
|
+
summary_lines = []
|
|
113
|
+
for line in lines[:10]: # Look at first 10 lines
|
|
114
|
+
stripped = line.strip()
|
|
115
|
+
if stripped and not stripped.startswith("#"):
|
|
116
|
+
summary_lines.append(stripped)
|
|
117
|
+
if len(summary_lines) >= 2:
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
summary = " ".join(summary_lines) if summary_lines else None
|
|
121
|
+
|
|
122
|
+
# Truncate summary if too long
|
|
123
|
+
if summary and len(summary) > 100:
|
|
124
|
+
summary = summary[:97] + "..."
|
|
125
|
+
|
|
126
|
+
# Return folder path, not plan.md file path
|
|
127
|
+
impl_folder = worktree_path / ".impl"
|
|
128
|
+
|
|
129
|
+
# Read issue reference if present
|
|
130
|
+
issue_ref = read_issue_reference(impl_folder)
|
|
131
|
+
issue_number = issue_ref.issue_number if issue_ref else None
|
|
132
|
+
issue_url = issue_ref.issue_url if issue_ref else None
|
|
133
|
+
|
|
134
|
+
return PlanStatus(
|
|
135
|
+
exists=True,
|
|
136
|
+
path=impl_folder,
|
|
137
|
+
summary=summary,
|
|
138
|
+
line_count=line_count,
|
|
139
|
+
first_lines=first_lines,
|
|
140
|
+
format="folder",
|
|
141
|
+
enriched_plan_path=enriched_plan_path,
|
|
142
|
+
enriched_plan_filename=enriched_plan_filename,
|
|
143
|
+
issue_number=issue_number,
|
|
144
|
+
issue_url=issue_url,
|
|
145
|
+
)
|