sase 0.1.0__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.
- sase/__init__.py +3 -0
- sase/__main__.py +6 -0
- sase/accept_workflow/__init__.py +26 -0
- sase/accept_workflow/conflict_check.py +226 -0
- sase/accept_workflow/parsing.py +183 -0
- sase/accept_workflow/renumber.py +549 -0
- sase/accept_workflow/workflow.py +395 -0
- sase/ace/AGENTS.md +24 -0
- sase/ace/CLAUDE.md +1 -0
- sase/ace/GEMINI.md +1 -0
- sase/ace/README.md +153 -0
- sase/ace/__init__.py +5 -0
- sase/ace/agent_runner.py +104 -0
- sase/ace/archive.py +185 -0
- sase/ace/changespec/__init__.py +160 -0
- sase/ace/changespec/locking.py +156 -0
- sase/ace/changespec/models.py +440 -0
- sase/ace/changespec/parser.py +346 -0
- sase/ace/changespec/raw_text.py +82 -0
- sase/ace/changespec/section_parsers.py +357 -0
- sase/ace/changespec/suffix_utils.py +78 -0
- sase/ace/changespec/validation.py +316 -0
- sase/ace/cl_status.py +37 -0
- sase/ace/comments/__init__.py +31 -0
- sase/ace/comments/core.py +117 -0
- sase/ace/comments/operations.py +456 -0
- sase/ace/constants.py +13 -0
- sase/ace/dismissed_agents.py +77 -0
- sase/ace/display.py +627 -0
- sase/ace/display_helpers.py +156 -0
- sase/ace/handlers/__init__.py +24 -0
- sase/ace/handlers/edit_hooks.py +304 -0
- sase/ace/handlers/mail.py +106 -0
- sase/ace/handlers/reword.py +375 -0
- sase/ace/handlers/show_diff.py +73 -0
- sase/ace/handlers/workflow_handlers.py +415 -0
- sase/ace/hint_types.py +29 -0
- sase/ace/hints.py +210 -0
- sase/ace/hooks/__init__.py +143 -0
- sase/ace/hooks/defaults.py +22 -0
- sase/ace/hooks/execution.py +672 -0
- sase/ace/hooks/history.py +81 -0
- sase/ace/hooks/mutations.py +505 -0
- sase/ace/hooks/processes.py +510 -0
- sase/ace/hooks/status.py +197 -0
- sase/ace/hooks/test_targets.py +176 -0
- sase/ace/hooks/timestamps.py +153 -0
- sase/ace/hooks/workflow_queries.py +170 -0
- sase/ace/last_selection.py +26 -0
- sase/ace/mail_ops.py +588 -0
- sase/ace/mentors.py +630 -0
- sase/ace/operations.py +261 -0
- sase/ace/query/__init__.py +62 -0
- sase/ace/query/evaluator.py +444 -0
- sase/ace/query/highlighting.py +285 -0
- sase/ace/query/parser.py +307 -0
- sase/ace/query/tokenizer.py +419 -0
- sase/ace/query/types.py +175 -0
- sase/ace/query_history.py +124 -0
- sase/ace/query_selection.py +52 -0
- sase/ace/restore.py +224 -0
- sase/ace/revert.py +190 -0
- sase/ace/saved_queries.py +165 -0
- sase/ace/saved_tag_names.py +79 -0
- sase/ace/scheduler/__init__.py +17 -0
- sase/ace/scheduler/checks_runner.py +528 -0
- sase/ace/scheduler/comments_handler.py +48 -0
- sase/ace/scheduler/hook_checks.py +538 -0
- sase/ace/scheduler/hooks_runner.py +655 -0
- sase/ace/scheduler/mentor_checks.py +630 -0
- sase/ace/scheduler/mentor_runner.py +223 -0
- sase/ace/scheduler/orphan_cleanup.py +68 -0
- sase/ace/scheduler/stale_running_cleanup.py +70 -0
- sase/ace/scheduler/suffix_transforms.py +374 -0
- sase/ace/scheduler/workflows_runner/__init__.py +16 -0
- sase/ace/scheduler/workflows_runner/completer.py +451 -0
- sase/ace/scheduler/workflows_runner/monitor.py +134 -0
- sase/ace/scheduler/workflows_runner/starter.py +597 -0
- sase/ace/status.py +37 -0
- sase/ace/sync_cache.py +106 -0
- sase/ace/tui/__init__.py +5 -0
- sase/ace/tui/_workflow_context.py +23 -0
- sase/ace/tui/actions/__init__.py +31 -0
- sase/ace/tui/actions/agent_workflow/__init__.py +21 -0
- sase/ace/tui/actions/agent_workflow/_agent_launch.py +411 -0
- sase/ace/tui/actions/agent_workflow/_editor.py +146 -0
- sase/ace/tui/actions/agent_workflow/_entry_points.py +223 -0
- sase/ace/tui/actions/agent_workflow/_prompt_bar.py +340 -0
- sase/ace/tui/actions/agent_workflow/_ref_resolution.py +143 -0
- sase/ace/tui/actions/agent_workflow/_types.py +26 -0
- sase/ace/tui/actions/agent_workflow/_workflow_exec.py +273 -0
- sase/ace/tui/actions/agents/__init__.py +9 -0
- sase/ace/tui/actions/agents/_core.py +309 -0
- sase/ace/tui/actions/agents/_folding.py +140 -0
- sase/ace/tui/actions/agents/_interaction.py +246 -0
- sase/ace/tui/actions/agents/_killing.py +469 -0
- sase/ace/tui/actions/agents/_notification_actions.py +463 -0
- sase/ace/tui/actions/agents/_notifications.py +196 -0
- sase/ace/tui/actions/agents/_revive.py +119 -0
- sase/ace/tui/actions/agents/_workflow_hitl.py +173 -0
- sase/ace/tui/actions/axe.py +264 -0
- sase/ace/tui/actions/axe_bgcmd.py +315 -0
- sase/ace/tui/actions/axe_display.py +358 -0
- sase/ace/tui/actions/base.py +695 -0
- sase/ace/tui/actions/changespec.py +434 -0
- sase/ace/tui/actions/clipboard.py +645 -0
- sase/ace/tui/actions/event_handlers.py +186 -0
- sase/ace/tui/actions/hints/__init__.py +19 -0
- sase/ace/tui/actions/hints/_accept.py +225 -0
- sase/ace/tui/actions/hints/_files.py +113 -0
- sase/ace/tui/actions/hints/_hooks.py +288 -0
- sase/ace/tui/actions/hints/_processing.py +290 -0
- sase/ace/tui/actions/hints/_rewind.py +138 -0
- sase/ace/tui/actions/hints/_types.py +44 -0
- sase/ace/tui/actions/marking.py +128 -0
- sase/ace/tui/actions/navigation/__init__.py +23 -0
- sase/ace/tui/actions/navigation/_advanced.py +197 -0
- sase/ace/tui/actions/navigation/_basic.py +222 -0
- sase/ace/tui/actions/navigation/_tree.py +283 -0
- sase/ace/tui/actions/navigation/_types.py +50 -0
- sase/ace/tui/actions/proposal_rebase.py +469 -0
- sase/ace/tui/actions/rename.py +245 -0
- sase/ace/tui/actions/sync.py +188 -0
- sase/ace/tui/app.py +481 -0
- sase/ace/tui/bgcmd.py +360 -0
- sase/ace/tui/changespec_history.py +141 -0
- sase/ace/tui/modals/__init__.py +69 -0
- sase/ace/tui/modals/agent_name_modal.py +65 -0
- sase/ace/tui/modals/base.py +80 -0
- sase/ace/tui/modals/command_history_modal.py +271 -0
- sase/ace/tui/modals/command_input_modal.py +76 -0
- sase/ace/tui/modals/confirm_delete_modal.py +53 -0
- sase/ace/tui/modals/confirm_kill_modal.py +53 -0
- sase/ace/tui/modals/help_modal/__init__.py +6 -0
- sase/ace/tui/modals/help_modal/bindings.py +235 -0
- sase/ace/tui/modals/help_modal/modal.py +250 -0
- sase/ace/tui/modals/help_modal/query_sections.py +236 -0
- sase/ace/tui/modals/hook_history_modal.py +183 -0
- sase/ace/tui/modals/notification_modal.py +374 -0
- sase/ace/tui/modals/parent_select_modal.py +42 -0
- sase/ace/tui/modals/plan_approval_modal.py +166 -0
- sase/ace/tui/modals/process_select_modal.py +152 -0
- sase/ace/tui/modals/project_select_modal.py +302 -0
- sase/ace/tui/modals/prompt_history_modal.py +298 -0
- sase/ace/tui/modals/query_edit_modal.py +64 -0
- sase/ace/tui/modals/rename_cl_modal.py +122 -0
- sase/ace/tui/modals/revive_agent_modal.py +263 -0
- sase/ace/tui/modals/runners_modal.py +515 -0
- sase/ace/tui/modals/status_modal.py +57 -0
- sase/ace/tui/modals/tag_input_modal.py +182 -0
- sase/ace/tui/modals/user_question_modal.py +568 -0
- sase/ace/tui/modals/workflow_hitl_modal.py +188 -0
- sase/ace/tui/modals/workflow_select_modal.py +42 -0
- sase/ace/tui/modals/workspace_input_modal.py +73 -0
- sase/ace/tui/modals/xprompt_select_modal.py +249 -0
- sase/ace/tui/models/__init__.py +21 -0
- sase/ace/tui/models/_loaders/__init__.py +31 -0
- sase/ace/tui/models/_loaders/_artifact_loaders.py +331 -0
- sase/ace/tui/models/_loaders/_changespec_loaders.py +159 -0
- sase/ace/tui/models/_loaders/_workflow_loaders.py +412 -0
- sase/ace/tui/models/_timestamps.py +156 -0
- sase/ace/tui/models/agent.py +331 -0
- sase/ace/tui/models/agent_loader.py +392 -0
- sase/ace/tui/models/fold_state.py +123 -0
- sase/ace/tui/models/workflow.py +102 -0
- sase/ace/tui/styles.tcss +1345 -0
- sase/ace/tui/thinking/__init__.py +18 -0
- sase/ace/tui/thinking/parser.py +218 -0
- sase/ace/tui/thinking/session_resolver.py +185 -0
- sase/ace/tui/widgets/__init__.py +39 -0
- sase/ace/tui/widgets/agent_detail.py +587 -0
- sase/ace/tui/widgets/agent_info_panel.py +76 -0
- sase/ace/tui/widgets/agent_list.py +378 -0
- sase/ace/tui/widgets/ancestors_children_panel.py +559 -0
- sase/ace/tui/widgets/axe_dashboard.py +489 -0
- sase/ace/tui/widgets/axe_info_panel.py +113 -0
- sase/ace/tui/widgets/bgcmd_list.py +174 -0
- sase/ace/tui/widgets/changespec_detail.py +430 -0
- sase/ace/tui/widgets/changespec_info_panel.py +59 -0
- sase/ace/tui/widgets/changespec_list.py +220 -0
- sase/ace/tui/widgets/comments_builder.py +67 -0
- sase/ace/tui/widgets/commits_builder.py +245 -0
- sase/ace/tui/widgets/file_panel.py +588 -0
- sase/ace/tui/widgets/hint_input_bar.py +171 -0
- sase/ace/tui/widgets/hint_tracker.py +15 -0
- sase/ace/tui/widgets/hooks_builder.py +286 -0
- sase/ace/tui/widgets/keybinding_footer.py +543 -0
- sase/ace/tui/widgets/mentors_builder.py +211 -0
- sase/ace/tui/widgets/notification_indicator.py +33 -0
- sase/ace/tui/widgets/prompt_input_bar.py +209 -0
- sase/ace/tui/widgets/prompt_panel/__init__.py +27 -0
- sase/ace/tui/widgets/prompt_panel/_agent_display.py +339 -0
- sase/ace/tui/widgets/prompt_panel/_helpers.py +250 -0
- sase/ace/tui/widgets/prompt_panel/_workflow_display.py +662 -0
- sase/ace/tui/widgets/section_builders.py +18 -0
- sase/ace/tui/widgets/suffix_formatting.py +145 -0
- sase/ace/tui/widgets/tab_bar.py +97 -0
- sase/ace/tui/widgets/thinking_panel.py +459 -0
- sase/ace/workflows/__init__.py +7 -0
- sase/ace/workflows/crs.py +231 -0
- sase/agent_names.py +155 -0
- sase/amend_workflow.py +255 -0
- sase/axe/__init__.py +78 -0
- sase/axe/check_cycles.py +276 -0
- sase/axe/chop_script_context.py +146 -0
- sase/axe/chop_script_runner.py +106 -0
- sase/axe/cli.py +185 -0
- sase/axe/config.py +112 -0
- sase/axe/hook_jobs.py +226 -0
- sase/axe/lumberjack.py +233 -0
- sase/axe/orchestrator.py +151 -0
- sase/axe/process.py +210 -0
- sase/axe/runner_pool.py +248 -0
- sase/axe/state.py +538 -0
- sase/axe_crs_runner.py +150 -0
- sase/axe_fix_hook_runner.py +262 -0
- sase/axe_mentor_runner.py +145 -0
- sase/axe_run_agent_runner.py +452 -0
- sase/axe_run_workflow_runner.py +276 -0
- sase/axe_runner_utils.py +116 -0
- sase/axe_summarize_hook_runner.py +171 -0
- sase/change_actions.py +651 -0
- sase/chat_history.py +235 -0
- sase/command_history.py +207 -0
- sase/commit_utils/__init__.py +41 -0
- sase/commit_utils/entries.py +439 -0
- sase/commit_utils/modifiers.py +392 -0
- sase/commit_utils/workspace.py +136 -0
- sase/commit_workflow/__init__.py +59 -0
- sase/commit_workflow/branch_info.py +23 -0
- sase/commit_workflow/changespec_operations.py +218 -0
- sase/commit_workflow/changespec_queries.py +75 -0
- sase/commit_workflow/cl_formatting.py +41 -0
- sase/commit_workflow/editor_utils.py +72 -0
- sase/commit_workflow/project_file_utils.py +45 -0
- sase/commit_workflow/workflow.py +303 -0
- sase/config.py +148 -0
- sase/crs_workflow.py +236 -0
- sase/default_config.yml +48 -0
- sase/gemini_wrapper/__init__.py +32 -0
- sase/gemini_wrapper/file_references.py +517 -0
- sase/gemini_wrapper/wrapper.py +168 -0
- sase/gh_workspace.py +425 -0
- sase/git_submit.py +329 -0
- sase/git_utils.py +49 -0
- sase/git_workspace.py +312 -0
- sase/github_config.py +14 -0
- sase/hook_history.py +120 -0
- sase/llm_provider/__init__.py +36 -0
- sase/llm_provider/_invoke.py +207 -0
- sase/llm_provider/_subprocess.py +200 -0
- sase/llm_provider/base.py +46 -0
- sase/llm_provider/claude.py +246 -0
- sase/llm_provider/config.py +25 -0
- sase/llm_provider/gemini.py +119 -0
- sase/llm_provider/postprocessing.py +244 -0
- sase/llm_provider/preprocessing.py +183 -0
- sase/llm_provider/registry.py +72 -0
- sase/llm_provider/types.py +27 -0
- sase/main/__init__.py +5 -0
- sase/main/cl_handler.py +171 -0
- sase/main/entry.py +389 -0
- sase/main/notify_handler.py +49 -0
- sase/main/parser.py +423 -0
- sase/main/plan_approve_handler.py +252 -0
- sase/main/query_handler/__init__.py +15 -0
- sase/main/query_handler/_editor.py +204 -0
- sase/main/query_handler/_query.py +471 -0
- sase/main/query_handler/_resume.py +76 -0
- sase/main/query_handler/special_cases.py +197 -0
- sase/main/user_question_handler.py +169 -0
- sase/main/utils.py +71 -0
- sase/mentor_config.py +190 -0
- sase/mentor_workflow.py +303 -0
- sase/metahook_config.py +107 -0
- sase/notifications/__init__.py +30 -0
- sase/notifications/models.py +55 -0
- sase/notifications/senders.py +148 -0
- sase/notifications/store.py +128 -0
- sase/plugin_discovery.py +54 -0
- sase/prompt_history.py +237 -0
- sase/py.typed +0 -0
- sase/renumber_utils.py +213 -0
- sase/rewind_workflow/__init__.py +9 -0
- sase/rewind_workflow/renumber.py +461 -0
- sase/rewind_workflow/workflow.py +199 -0
- sase/rich_utils.py +222 -0
- sase/running_field.py +575 -0
- sase/sase_utils.py +235 -0
- sase/scripts/__init__.py +142 -0
- sase/scripts/gh_setup.py +52 -0
- sase/scripts/git_setup.py +53 -0
- sase/scripts/hg_setup.py +71 -0
- sase/scripts/new_pr_desc_get_context.py +88 -0
- sase/scripts/pr_create_changespec.py +58 -0
- sase/scripts/sase_chop_cl_submitted_checks.py +28 -0
- sase/scripts/sase_chop_comment_checks.py +28 -0
- sase/scripts/sase_chop_comment_zombie_checks.py +37 -0
- sase/scripts/sase_chop_error_digest.py +29 -0
- sase/scripts/sase_chop_hook_checks.py +37 -0
- sase/scripts/sase_chop_mentor_checks.py +37 -0
- sase/scripts/sase_chop_orphan_cleanup.py +37 -0
- sase/scripts/sase_chop_pending_checks_poll.py +37 -0
- sase/scripts/sase_chop_stale_running_cleanup.py +33 -0
- sase/scripts/sase_chop_suffix_transforms.py +38 -0
- sase/scripts/sase_chop_wait_checks.py +91 -0
- sase/scripts/sase_chop_workflow_checks.py +37 -0
- sase/scripts/sase_commit_workflow +357 -0
- sase/scripts/sase_json_workflow +80 -0
- sase/scripts/sase_migrate_statuses +176 -0
- sase/scripts/sase_split_prepare_execute +116 -0
- sase/scripts/sase_split_setup +55 -0
- sase/scripts/sync_attempt.py +39 -0
- sase/scripts/sync_report.py +30 -0
- sase/scripts/sync_setup.py +36 -0
- sase/shared_utils.py +309 -0
- sase/status_state_machine/__init__.py +49 -0
- sase/status_state_machine/constants.py +88 -0
- sase/status_state_machine/field_updates.py +409 -0
- sase/status_state_machine/mail_suffix.py +96 -0
- sase/status_state_machine/transitions.py +636 -0
- sase/summarize_utils.py +37 -0
- sase/summarize_workflow.py +140 -0
- sase/vcs_provider/__init__.py +39 -0
- sase/vcs_provider/_base.py +234 -0
- sase/vcs_provider/_command_runner.py +105 -0
- sase/vcs_provider/_errors.py +21 -0
- sase/vcs_provider/_hookspec.py +154 -0
- sase/vcs_provider/_plugin_manager.py +201 -0
- sase/vcs_provider/_registry.py +174 -0
- sase/vcs_provider/_types.py +20 -0
- sase/vcs_provider/config.py +43 -0
- sase/vcs_provider/plugins/__init__.py +1 -0
- sase/vcs_provider/plugins/_git_common.py +301 -0
- sase/vcs_provider/plugins/bare_git.py +30 -0
- sase/workflow_base.py +27 -0
- sase/workflow_utils.py +194 -0
- sase/workspace_changespec.py +147 -0
- sase/xprompt/__init__.py +120 -0
- sase/xprompt/_disabled_regions.py +78 -0
- sase/xprompt/_exceptions.py +19 -0
- sase/xprompt/_fenced_blocks.py +52 -0
- sase/xprompt/_jinja.py +244 -0
- sase/xprompt/_parsing.py +471 -0
- sase/xprompt/_step_input_loader.py +70 -0
- sase/xprompt/directives.py +170 -0
- sase/xprompt/loader.py +747 -0
- sase/xprompt/models.py +250 -0
- sase/xprompt/output_validation.py +487 -0
- sase/xprompt/processor.py +304 -0
- sase/xprompt/workflow_executor.py +497 -0
- sase/xprompt/workflow_executor_loops.py +439 -0
- sase/xprompt/workflow_executor_parallel.py +362 -0
- sase/xprompt/workflow_executor_steps.py +49 -0
- sase/xprompt/workflow_executor_steps_embedded.py +593 -0
- sase/xprompt/workflow_executor_steps_prompt.py +409 -0
- sase/xprompt/workflow_executor_steps_script.py +363 -0
- sase/xprompt/workflow_executor_types.py +82 -0
- sase/xprompt/workflow_executor_utils.py +128 -0
- sase/xprompt/workflow_hitl.py +317 -0
- sase/xprompt/workflow_loader.py +347 -0
- sase/xprompt/workflow_loader_parse.py +430 -0
- sase/xprompt/workflow_models.py +280 -0
- sase/xprompt/workflow_output.py +432 -0
- sase/xprompt/workflow_runner.py +407 -0
- sase/xprompt/workflow_validator.py +579 -0
- sase-0.1.0.dist-info/METADATA +26 -0
- sase-0.1.0.dist-info/RECORD +370 -0
- sase-0.1.0.dist-info/WHEEL +4 -0
- sase-0.1.0.dist-info/entry_points.txt +22 -0
sase/__init__.py
ADDED
sase/__main__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Accept workflow package for accepting proposed COMMITS entries."""
|
|
2
|
+
|
|
3
|
+
from .conflict_check import ConflictCheckResult, ConflictPair, run_conflict_check
|
|
4
|
+
from .parsing import (
|
|
5
|
+
expand_shorthand_proposals,
|
|
6
|
+
find_proposal_entry,
|
|
7
|
+
parse_proposal_entries,
|
|
8
|
+
parse_proposal_entries_with_shorthand,
|
|
9
|
+
parse_proposal_id,
|
|
10
|
+
)
|
|
11
|
+
from .renumber import renumber_commit_entries
|
|
12
|
+
from .workflow import AcceptWorkflow, main
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AcceptWorkflow",
|
|
16
|
+
"ConflictCheckResult",
|
|
17
|
+
"ConflictPair",
|
|
18
|
+
"expand_shorthand_proposals",
|
|
19
|
+
"find_proposal_entry",
|
|
20
|
+
"main",
|
|
21
|
+
"parse_proposal_entries",
|
|
22
|
+
"parse_proposal_entries_with_shorthand",
|
|
23
|
+
"parse_proposal_id",
|
|
24
|
+
"renumber_commit_entries",
|
|
25
|
+
"run_conflict_check",
|
|
26
|
+
]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Conflict checking for accept workflow proposals."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from itertools import combinations
|
|
5
|
+
|
|
6
|
+
from sase.ace.changespec import CommitEntry
|
|
7
|
+
from sase.commit_utils import apply_diffs_to_workspace, clean_workspace
|
|
8
|
+
from sase.rich_utils import print_status
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ConflictPair:
|
|
13
|
+
"""A pair of proposals that conflict with each other."""
|
|
14
|
+
|
|
15
|
+
proposal_a: tuple[int, str] # (base_num, letter)
|
|
16
|
+
proposal_b: tuple[int, str]
|
|
17
|
+
error_message: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ConflictCheckResult:
|
|
22
|
+
"""Result of a conflict check."""
|
|
23
|
+
|
|
24
|
+
success: bool
|
|
25
|
+
failed_proposal: tuple[int, str] | None
|
|
26
|
+
conflicting_pairs: list[ConflictPair]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _format_proposal_id(base_num: int, letter: str) -> str:
|
|
30
|
+
"""Format a proposal ID for display.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
base_num: The base number of the proposal.
|
|
34
|
+
letter: The proposal letter.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Formatted proposal ID like "(2a)".
|
|
38
|
+
"""
|
|
39
|
+
return f"({base_num}{letter})"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _apply_all_proposals(
|
|
43
|
+
workspace_dir: str,
|
|
44
|
+
proposals: list[tuple[int, str, CommitEntry]],
|
|
45
|
+
) -> tuple[bool, str]:
|
|
46
|
+
"""Apply all proposals together and check if they succeed.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
workspace_dir: The workspace directory.
|
|
50
|
+
proposals: List of (base_num, letter, entry) tuples.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Tuple of (success, error_message).
|
|
54
|
+
"""
|
|
55
|
+
diff_paths = []
|
|
56
|
+
for _base_num, _letter, entry in proposals:
|
|
57
|
+
assert entry.diff is not None
|
|
58
|
+
diff_paths.append(entry.diff)
|
|
59
|
+
|
|
60
|
+
return apply_diffs_to_workspace(workspace_dir, diff_paths)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _find_conflicting_pairs(
|
|
64
|
+
workspace_dir: str,
|
|
65
|
+
proposals: list[tuple[int, str, CommitEntry]],
|
|
66
|
+
) -> list[ConflictPair]:
|
|
67
|
+
"""Find all unique pairs of proposals that conflict.
|
|
68
|
+
|
|
69
|
+
Tests each unique pair by applying both diffs together in a single command.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
workspace_dir: The workspace directory.
|
|
73
|
+
proposals: List of (base_num, letter, entry) tuples.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of ConflictPair for each pair that conflicts.
|
|
77
|
+
"""
|
|
78
|
+
conflicting_pairs: list[ConflictPair] = []
|
|
79
|
+
|
|
80
|
+
for (num_a, letter_a, entry_a), (num_b, letter_b, entry_b) in combinations(
|
|
81
|
+
proposals, 2
|
|
82
|
+
):
|
|
83
|
+
# Clean workspace before each pair test
|
|
84
|
+
clean_workspace(workspace_dir)
|
|
85
|
+
|
|
86
|
+
# Apply both proposals together
|
|
87
|
+
assert entry_a.diff is not None
|
|
88
|
+
assert entry_b.diff is not None
|
|
89
|
+
success, error_msg = apply_diffs_to_workspace(
|
|
90
|
+
workspace_dir, [entry_a.diff, entry_b.diff]
|
|
91
|
+
)
|
|
92
|
+
if not success:
|
|
93
|
+
conflicting_pairs.append(
|
|
94
|
+
ConflictPair(
|
|
95
|
+
proposal_a=(num_a, letter_a),
|
|
96
|
+
proposal_b=(num_b, letter_b),
|
|
97
|
+
error_message=error_msg,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return conflicting_pairs
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run_conflict_check(
|
|
105
|
+
workspace_dir: str,
|
|
106
|
+
validated_proposals: list[tuple[int, str, str | None, CommitEntry]],
|
|
107
|
+
verbose: bool = True,
|
|
108
|
+
) -> ConflictCheckResult:
|
|
109
|
+
"""Run conflict check on a set of proposals.
|
|
110
|
+
|
|
111
|
+
This function tests whether all proposals can be applied together using
|
|
112
|
+
a single hg import command. If they fail and there are >2 proposals,
|
|
113
|
+
it identifies which specific pairs conflict.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
workspace_dir: The workspace directory to test in.
|
|
117
|
+
validated_proposals: List of (base_num, letter, msg, entry) tuples.
|
|
118
|
+
The msg field is ignored for conflict checking.
|
|
119
|
+
verbose: If True, print progress messages.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
ConflictCheckResult indicating success or failure details.
|
|
123
|
+
"""
|
|
124
|
+
# Edge case: 0-1 proposals can't conflict
|
|
125
|
+
if len(validated_proposals) <= 1:
|
|
126
|
+
return ConflictCheckResult(
|
|
127
|
+
success=True,
|
|
128
|
+
failed_proposal=None,
|
|
129
|
+
conflicting_pairs=[],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Extract just what we need for conflict checking
|
|
133
|
+
proposals = [
|
|
134
|
+
(base_num, letter, entry)
|
|
135
|
+
for base_num, letter, _msg, entry in validated_proposals
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
if verbose:
|
|
139
|
+
print_status(
|
|
140
|
+
f"Running conflict check on {len(proposals)} proposals...",
|
|
141
|
+
"progress",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Try applying all proposals together
|
|
145
|
+
success, error_msg = _apply_all_proposals(workspace_dir, proposals)
|
|
146
|
+
|
|
147
|
+
# Clean workspace after test
|
|
148
|
+
clean_workspace(workspace_dir)
|
|
149
|
+
|
|
150
|
+
if success:
|
|
151
|
+
return ConflictCheckResult(
|
|
152
|
+
success=True,
|
|
153
|
+
failed_proposal=None,
|
|
154
|
+
conflicting_pairs=[],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Proposals failed to apply together
|
|
158
|
+
if verbose:
|
|
159
|
+
print_status(
|
|
160
|
+
f"Conflict detected when applying proposals together: {error_msg}",
|
|
161
|
+
"error",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# For 2 proposals, no need to check pairs - we know they conflict
|
|
165
|
+
if len(proposals) == 2:
|
|
166
|
+
if verbose:
|
|
167
|
+
print_status(
|
|
168
|
+
"Accept aborted. Try accepting non-conflicting proposals separately.",
|
|
169
|
+
"error",
|
|
170
|
+
)
|
|
171
|
+
return ConflictCheckResult(
|
|
172
|
+
success=False,
|
|
173
|
+
failed_proposal=None, # Can't determine which one failed
|
|
174
|
+
conflicting_pairs=[],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# For >2 proposals, find which specific pairs conflict
|
|
178
|
+
if verbose:
|
|
179
|
+
print_status("Checking which proposals conflict...", "progress")
|
|
180
|
+
|
|
181
|
+
conflicting_pairs = _find_conflicting_pairs(workspace_dir, proposals)
|
|
182
|
+
|
|
183
|
+
# Clean workspace after pair testing
|
|
184
|
+
clean_workspace(workspace_dir)
|
|
185
|
+
|
|
186
|
+
if verbose:
|
|
187
|
+
for pair in conflicting_pairs:
|
|
188
|
+
print_status(
|
|
189
|
+
f"Conflicting pair: {_format_proposal_id(*pair.proposal_a)} and "
|
|
190
|
+
f"{_format_proposal_id(*pair.proposal_b)}",
|
|
191
|
+
"error",
|
|
192
|
+
)
|
|
193
|
+
print_status(
|
|
194
|
+
"Accept aborted. Try accepting non-conflicting proposals separately.",
|
|
195
|
+
"error",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return ConflictCheckResult(
|
|
199
|
+
success=False,
|
|
200
|
+
failed_proposal=None, # Can't determine which one failed
|
|
201
|
+
conflicting_pairs=conflicting_pairs,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def format_conflict_message(result: ConflictCheckResult) -> str:
|
|
206
|
+
"""Format conflict information for display.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
result: The conflict check result.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
A formatted message describing the conflicts.
|
|
213
|
+
"""
|
|
214
|
+
lines: list[str] = []
|
|
215
|
+
|
|
216
|
+
# Add conflicting pair messages (only if there are any)
|
|
217
|
+
for pair in result.conflicting_pairs:
|
|
218
|
+
lines.append(
|
|
219
|
+
f"Conflicting pair: {_format_proposal_id(*pair.proposal_a)} and "
|
|
220
|
+
f"{_format_proposal_id(*pair.proposal_b)}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Always add the abort message
|
|
224
|
+
lines.append("Accept aborted. Try accepting non-conflicting proposals separately.")
|
|
225
|
+
|
|
226
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Proposal parsing and lookup functions for accept workflow."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from sase.ace.changespec import CommitEntry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_proposal_id(proposal_id: str) -> tuple[int, str] | None:
|
|
9
|
+
"""Parse a proposal ID into base number and letter.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
proposal_id: The proposal ID (e.g., "2a", "2b").
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Tuple of (base_number, letter) or None if invalid.
|
|
16
|
+
"""
|
|
17
|
+
match = re.match(r"^(\d+)([a-z])$", proposal_id)
|
|
18
|
+
if not match:
|
|
19
|
+
return None
|
|
20
|
+
return int(match.group(1)), match.group(2)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_proposal_entries(args: list[str]) -> list[tuple[str, str | None]] | None:
|
|
24
|
+
"""Parse proposal entry arguments into (id, msg) tuples.
|
|
25
|
+
|
|
26
|
+
Supports:
|
|
27
|
+
- New syntax: "2b(Add foobar field)" - id with optional message in parentheses
|
|
28
|
+
- Legacy syntax: "2b" followed by optional separate message argument
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
args: List of arguments (e.g., ["2a(msg)", "2b"] or ["2a", "msg"]).
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of (id, msg) tuples or None if invalid format.
|
|
35
|
+
"""
|
|
36
|
+
if not args:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Regex patterns
|
|
40
|
+
id_with_msg_pattern = re.compile(r"^(\d+[a-z])\((.+)\)$") # "2a(msg)"
|
|
41
|
+
bare_id_pattern = re.compile(r"^(\d+[a-z])$") # "2a"
|
|
42
|
+
|
|
43
|
+
entries: list[tuple[str, str | None]] = []
|
|
44
|
+
|
|
45
|
+
i = 0
|
|
46
|
+
while i < len(args):
|
|
47
|
+
arg = args[i]
|
|
48
|
+
|
|
49
|
+
# Check for new syntax with message in parentheses: "2a(msg)"
|
|
50
|
+
match_with_msg = id_with_msg_pattern.match(arg)
|
|
51
|
+
if match_with_msg:
|
|
52
|
+
proposal_id = match_with_msg.group(1)
|
|
53
|
+
msg = match_with_msg.group(2)
|
|
54
|
+
entries.append((proposal_id, msg))
|
|
55
|
+
i += 1
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Check for bare ID: "2a"
|
|
59
|
+
match_bare = bare_id_pattern.match(arg)
|
|
60
|
+
if match_bare:
|
|
61
|
+
proposal_id = match_bare.group(1)
|
|
62
|
+
# Check if next arg is a message (not another proposal ID)
|
|
63
|
+
if i + 1 < len(args):
|
|
64
|
+
next_arg = args[i + 1]
|
|
65
|
+
# If next arg doesn't look like a proposal ID (with or without msg),
|
|
66
|
+
# treat it as a legacy message
|
|
67
|
+
if not id_with_msg_pattern.match(
|
|
68
|
+
next_arg
|
|
69
|
+
) and not bare_id_pattern.match(next_arg):
|
|
70
|
+
entries.append((proposal_id, next_arg))
|
|
71
|
+
i += 2
|
|
72
|
+
continue
|
|
73
|
+
entries.append((proposal_id, None))
|
|
74
|
+
i += 1
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Invalid format
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
return entries if entries else None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def expand_shorthand_proposals(
|
|
84
|
+
args: list[str],
|
|
85
|
+
last_accepted_base: str | None,
|
|
86
|
+
) -> list[str] | None:
|
|
87
|
+
"""Expand shorthand proposal entries to full IDs.
|
|
88
|
+
|
|
89
|
+
Shorthand format:
|
|
90
|
+
- "a" -> "2a" (where 2 is last_accepted_base)
|
|
91
|
+
- "a(msg)" -> "2a(msg)"
|
|
92
|
+
|
|
93
|
+
Full format (pass through unchanged):
|
|
94
|
+
- "2a", "2b(msg)", etc.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
args: List of arguments (may contain shorthand or full IDs).
|
|
98
|
+
last_accepted_base: The base number to prepend (e.g., "2").
|
|
99
|
+
If None, shorthand cannot be expanded.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of expanded arguments, or None if shorthand used but
|
|
103
|
+
last_accepted_base is not available or invalid format encountered.
|
|
104
|
+
"""
|
|
105
|
+
# Regex patterns
|
|
106
|
+
shorthand_bare = re.compile(r"^([a-z])$") # "a"
|
|
107
|
+
shorthand_with_msg = re.compile(r"^([a-z])\((.+)\)$") # "a(msg)"
|
|
108
|
+
full_id_bare = re.compile(r"^\d+[a-z]$") # "2a"
|
|
109
|
+
full_id_with_msg = re.compile(r"^\d+[a-z]\(.+\)$") # "2a(msg)"
|
|
110
|
+
|
|
111
|
+
expanded: list[str] = []
|
|
112
|
+
|
|
113
|
+
for arg in args:
|
|
114
|
+
# Check if already full format
|
|
115
|
+
if full_id_bare.match(arg) or full_id_with_msg.match(arg):
|
|
116
|
+
expanded.append(arg)
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# Check for shorthand bare letter
|
|
120
|
+
match_bare = shorthand_bare.match(arg)
|
|
121
|
+
if match_bare:
|
|
122
|
+
if last_accepted_base is None:
|
|
123
|
+
return None # Cannot expand without base
|
|
124
|
+
letter = match_bare.group(1)
|
|
125
|
+
expanded.append(f"{last_accepted_base}{letter}")
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Check for shorthand letter with message
|
|
129
|
+
match_with_msg = shorthand_with_msg.match(arg)
|
|
130
|
+
if match_with_msg:
|
|
131
|
+
if last_accepted_base is None:
|
|
132
|
+
return None
|
|
133
|
+
letter = match_with_msg.group(1)
|
|
134
|
+
msg = match_with_msg.group(2)
|
|
135
|
+
expanded.append(f"{last_accepted_base}{letter}({msg})")
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
# Invalid format
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
return expanded
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def parse_proposal_entries_with_shorthand(
|
|
145
|
+
args: list[str],
|
|
146
|
+
last_accepted_base: str | None,
|
|
147
|
+
) -> list[tuple[str, str | None]] | None:
|
|
148
|
+
"""Parse proposal entries with shorthand expansion support.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
args: List of arguments (shorthand or full format).
|
|
152
|
+
last_accepted_base: Base number for shorthand expansion.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
List of (id, msg) tuples or None if invalid.
|
|
156
|
+
"""
|
|
157
|
+
expanded = expand_shorthand_proposals(args, last_accepted_base)
|
|
158
|
+
if expanded is None:
|
|
159
|
+
return None
|
|
160
|
+
return parse_proposal_entries(expanded)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def find_proposal_entry(
|
|
164
|
+
commits: list[CommitEntry] | None,
|
|
165
|
+
base_number: int,
|
|
166
|
+
letter: str,
|
|
167
|
+
) -> CommitEntry | None:
|
|
168
|
+
"""Find a proposal entry in commits by base number and letter.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
commits: List of commit entries.
|
|
172
|
+
base_number: The base number (e.g., 2 for "2a").
|
|
173
|
+
letter: The proposal letter (e.g., "a" for "2a").
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The matching CommitEntry or None if not found.
|
|
177
|
+
"""
|
|
178
|
+
if not commits:
|
|
179
|
+
return None
|
|
180
|
+
for entry in commits:
|
|
181
|
+
if entry.number == base_number and entry.proposal_letter == letter:
|
|
182
|
+
return entry
|
|
183
|
+
return None
|