claude-dev-env 1.38.0 → 1.39.0
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.
- package/CLAUDE.md +10 -36
- package/_shared/pr-loop/audit-reply-template.md +147 -0
- package/_shared/pr-loop/fix-protocol.md +25 -4
- package/_shared/pr-loop/gh-payloads.md +37 -50
- package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
- package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +189 -0
- package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
- package/_shared/pr-loop/state-schema.md +1 -1
- package/agents/clean-coder.md +2 -2
- package/bin/install.mjs +6 -7
- package/bin/install.test.mjs +8 -0
- package/commands/doc-gist.md +16 -0
- package/commands/plan.md +0 -2
- package/commands/review-plan.md +1 -1
- package/docs/CODE_RULES.md +122 -2
- package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
- package/hooks/blocking/code_rules_enforcer.py +1236 -161
- package/hooks/blocking/convergence_gate_blocker.py +130 -0
- package/hooks/blocking/destructive_command_blocker.py +74 -0
- package/hooks/blocking/gh_body_arg_blocker.py +30 -0
- package/hooks/blocking/md_to_html_blocker.py +119 -0
- package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
- package/hooks/blocking/test_code_rules_enforcer.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
- package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
- package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
- package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
- package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +158 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
- package/hooks/blocking/test_destructive_command_blocker.py +146 -0
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
- package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
- package/hooks/blocking/test_md_to_html_blocker.py +317 -0
- package/hooks/config/any_type_config.py +7 -0
- package/hooks/config/banned_identifiers_constants.py +11 -0
- package/hooks/config/blocking_check_limits.py +38 -0
- package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
- package/hooks/config/code_rules_enforcer_constants.py +53 -0
- package/hooks/config/convergence_branch_constants.py +9 -0
- package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
- package/hooks/config/html_companion_constants.py +20 -0
- package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
- package/hooks/config/test_banned_identifiers_constants.py +17 -0
- package/hooks/hooks.json +28 -20
- package/hooks/pyproject.toml +69 -0
- package/hooks/validators/mypy_integration.py +47 -1
- package/hooks/validators/run_all_validators.py +3 -3
- package/hooks/validators/test_mypy_integration.py +50 -1
- package/hooks/workflow/doc_gist_auto_publish.py +144 -0
- package/hooks/workflow/md_to_html_companion.py +365 -0
- package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
- package/hooks/workflow/test_md_to_html_companion.py +452 -0
- package/package.json +1 -1
- package/rules/gh-body-file.md +2 -0
- package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
- package/scripts/check.ps1 +106 -0
- package/scripts/config/timing.py +11 -0
- package/scripts/sweep_empty_dirs.py +138 -0
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_sweep_empty_dirs.py +183 -0
- package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
- package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
- package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
- package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
- package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
- package/skills/bugteam/CONSTRAINTS.md +21 -22
- package/skills/bugteam/EXAMPLES.md +3 -3
- package/skills/bugteam/PROMPTS.md +227 -67
- package/skills/bugteam/SKILL.md +114 -455
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +112 -39
- package/skills/bugteam/reference/audit-contract.md +4 -22
- package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
- package/skills/bugteam/reference/design-rationale.md +2 -2
- package/skills/bugteam/reference/github-pr-reviews.md +50 -57
- package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
- package/skills/bugteam/reference/team-setup.md +106 -9
- package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
- package/skills/bugteam/scripts/README.md +60 -0
- package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
- package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
- package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
- package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
- package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
- package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
- package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
- package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
- package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
- package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
- package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
- package/skills/bugteam/test_skill_additions.py +1 -11
- package/skills/code/SKILL.md +176 -0
- package/skills/doc-gist/SKILL.md +99 -0
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
- package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
- package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
- package/skills/doc-gist/references/examples/05-design-system.html +629 -0
- package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
- package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
- package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
- package/skills/doc-gist/references/examples/11-status-report.html +528 -0
- package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
- package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
- package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
- package/skills/doc-gist/references/examples/README.md +5 -0
- package/skills/doc-gist/scripts/config/__init__.py +0 -0
- package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
- package/skills/doc-gist/scripts/gist_upload.py +177 -0
- package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
- package/skills/findbugs/SKILL.md +68 -2
- package/skills/monitor-open-prs/SKILL.md +13 -32
- package/skills/monitor-open-prs/test_skill_contract.py +0 -11
- package/skills/pr-consistency-audit/SKILL.md +112 -0
- package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
- package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
- package/skills/pr-converge/SKILL.md +227 -23
- package/skills/pr-converge/config/__init__.py +0 -0
- package/skills/pr-converge/config/constants.py +62 -0
- package/skills/pr-converge/reference/convergence-gates.md +138 -44
- package/skills/pr-converge/reference/examples.md +43 -11
- package/skills/pr-converge/reference/fix-protocol.md +6 -5
- package/skills/pr-converge/reference/ground-rules.md +5 -3
- package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
- package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
- package/skills/pr-converge/reference/per-tick.md +90 -31
- package/skills/pr-converge/reference/state-schema.md +22 -1
- package/skills/pr-converge/reference/stop-conditions.md +9 -7
- package/skills/pr-converge/scripts/README.md +34 -46
- package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
- package/skills/pr-converge/scripts/check_convergence.py +497 -0
- package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
- package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
- package/skills/qbug/SKILL.md +132 -27
- package/skills/session-log/SKILL.md +216 -114
- package/skills/session-tidy/SKILL.md +1 -1
- package/skills/skill-builder/SKILL.md +138 -56
- package/skills/skill-builder/references/delegation-map.md +72 -113
- package/skills/skill-builder/references/progressive-disclosure.md +122 -0
- package/skills/skill-builder/references/self-audit-checklist.md +92 -0
- package/skills/skill-builder/references/skill-types.md +228 -0
- package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
- package/skills/skill-builder/templates/gap-analysis.md +15 -8
- package/skills/skill-builder/workflows/improve-skill.md +86 -57
- package/skills/skill-builder/workflows/new-skill.md +80 -168
- package/skills/skill-builder/workflows/polish-skill.md +78 -54
- package/skills/structure-prompt/SKILL.md +50 -0
- package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
- package/skills/structure-prompt/reference/block-classification.md +27 -0
- package/skills/structure-prompt/reference/canonical-case.md +48 -0
- package/skills/structure-prompt/reference/citation-depth.md +70 -0
- package/skills/structure-prompt/reference/cleanup.md +33 -0
- package/skills/structure-prompt/reference/constraints.md +33 -0
- package/skills/structure-prompt/reference/directives.md +37 -0
- package/skills/structure-prompt/reference/examples.md +72 -0
- package/skills/structure-prompt/reference/instantiation.md +51 -0
- package/skills/structure-prompt/reference/output-contract.md +72 -0
- package/skills/structure-prompt/reference/per-category.md +23 -0
- package/skills/structure-prompt/reference/persona.md +38 -0
- package/skills/structure-prompt/reference/research.md +33 -0
- package/skills/structure-prompt/reference/structure.md +28 -0
- package/agents/code-standards-agent.md +0 -93
- package/agents/groq-coder.md +0 -113
- package/agents/plan-executor.md +0 -226
- package/agents/project-docs-analyzer.md +0 -53
- package/agents/project-structure-organizer-agent.md +0 -72
- package/agents/skill-to-agent-converter.md +0 -370
- package/agents/skill-writer-agent.md +0 -470
- package/agents/user-docs-writer.md +0 -67
- package/agents/workflow-visual-documenter.md +0 -82
- package/commands/readability-review.md +0 -20
- package/hooks/mypy.ini +0 -2
- package/hooks/notification/attention_needed_notify.py +0 -71
- package/hooks/notification/claude_notification_handler.py +0 -67
- package/hooks/notification/notification_utils.py +0 -267
- package/hooks/notification/subagent_complete_notify.py +0 -381
- package/hooks/notification/test_attention_needed_notify.py +0 -47
- package/hooks/notification/test_claude_notification_handler.py +0 -54
- package/hooks/notification/test_notification_utils.py +0 -91
- package/hooks/notification/test_subagent_complete_notify.py +0 -79
- package/scripts/config/groq_bugteam_config.py +0 -230
- package/scripts/config/test_groq_bugteam_config.py +0 -83
- package/scripts/config/test_spec_implementer_prompt.py +0 -32
- package/scripts/groq_bugteam.README.md +0 -131
- package/scripts/groq_bugteam.py +0 -647
- package/scripts/groq_bugteam_dotenv.py +0 -40
- package/scripts/groq_bugteam_spec.py +0 -226
- package/scripts/test_groq_bugteam.py +0 -529
- package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
- package/scripts/test_groq_bugteam_dotenv.py +0 -66
- package/scripts/test_groq_bugteam_spec.py +0 -338
- package/skills/bugteam/SKILL_EVALS.md +0 -309
- package/skills/dream/SKILL.md +0 -118
- package/skills/ingest/SKILL.md +0 -40
- package/skills/npm-creator/SKILL.md +0 -187
- package/skills/readability-review/SKILL.md +0 -127
- package/skills/resume-review/SKILL.md +0 -261
- package/skills/rule-audit/SKILL.md +0 -307
- package/skills/rule-creator/SKILL.md +0 -150
- package/skills/searching-obsidian-vault/SKILL.md +0 -131
- package/skills/skill-writer/REFERENCE.md +0 -284
- package/skills/skill-writer/SKILL.md +0 -222
- package/skills/tdd-team/SKILL.md +0 -128
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Create a loop-state.json file for a bugteam run.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python scripts/init_loop_state.py --pr-number 422 --head-ref feat/branch --starting-sha abc1234 [--is-multi-pr]
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
_self_dir = Path(__file__).resolve().parent
|
|
15
|
+
if str(_self_dir) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(_self_dir))
|
|
17
|
+
|
|
18
|
+
from _path_resolver import (
|
|
19
|
+
build_run_name,
|
|
20
|
+
per_pr_workspace,
|
|
21
|
+
resolve_run_temp_dir,
|
|
22
|
+
)
|
|
23
|
+
from config.path_resolver_constants import LOOP_STATE_JSON_INDENT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_loop_state(
|
|
27
|
+
*,
|
|
28
|
+
pr_number: int,
|
|
29
|
+
head_ref: str,
|
|
30
|
+
starting_sha: str,
|
|
31
|
+
is_multi_pr: bool = False,
|
|
32
|
+
) -> Path:
|
|
33
|
+
"""Create the loop-state.json file and return its path.
|
|
34
|
+
|
|
35
|
+
The written state dict carries the subset of the keys documented
|
|
36
|
+
in `_shared/pr-loop/state-schema.md` common-fields table that are
|
|
37
|
+
initialized at loop creation. Fields populated only during the loop
|
|
38
|
+
(e.g. `audit_log`) are added by later steps and are not written
|
|
39
|
+
here:
|
|
40
|
+
|
|
41
|
+
- `loop_count: 0` (int counter, bumps on each AUDIT or tick)
|
|
42
|
+
- `last_action: "fresh"` (enum: fresh | audited | fixed)
|
|
43
|
+
- `last_findings: {p0: 0, p1: 0, p2: 0, total: 0}` (count dict
|
|
44
|
+
populated by AUDIT)
|
|
45
|
+
- `starting_sha: <str>` (the SHA passed in)
|
|
46
|
+
- `loop_comment_index: {}` (dict keyed by finding_id; AUDIT
|
|
47
|
+
populates `finding_comment_id`, `finding_comment_url`, and
|
|
48
|
+
`thread_node_id` per entry when it posts the per-loop review,
|
|
49
|
+
and FIX sets `fix_status` when its commit lands)
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
pr_number: Pull request number.
|
|
53
|
+
head_ref: Head branch ref.
|
|
54
|
+
starting_sha: Starting commit SHA.
|
|
55
|
+
is_multi_pr: Whether multi-PR mode is active.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Path to the created loop-state.json file.
|
|
59
|
+
"""
|
|
60
|
+
run_name = build_run_name(pr_number, head_ref, is_multi_pr=is_multi_pr)
|
|
61
|
+
run_temp_dir = resolve_run_temp_dir(run_name)
|
|
62
|
+
workspace = per_pr_workspace(run_temp_dir, "", "", pr_number)
|
|
63
|
+
worktree_path = workspace["worktree"]
|
|
64
|
+
assert isinstance(worktree_path, Path)
|
|
65
|
+
|
|
66
|
+
worktree_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
state_path = worktree_path / "loop-state.json"
|
|
68
|
+
|
|
69
|
+
state = {
|
|
70
|
+
"loop_count": 0,
|
|
71
|
+
"last_action": "fresh",
|
|
72
|
+
"last_findings": {"p0": 0, "p1": 0, "p2": 0, "total": 0},
|
|
73
|
+
"starting_sha": starting_sha,
|
|
74
|
+
"loop_comment_index": {},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
state_path.write_text(
|
|
78
|
+
json.dumps(state, indent=LOOP_STATE_JSON_INDENT) + "\n",
|
|
79
|
+
encoding="utf-8",
|
|
80
|
+
)
|
|
81
|
+
return state_path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
85
|
+
"""Parse command-line arguments.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
all_argv: Command-line argument list.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Parsed namespace with pr_number, head_ref, starting_sha, and is_multi_pr.
|
|
92
|
+
"""
|
|
93
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
94
|
+
parser.add_argument("--pr-number", type=int, required=True)
|
|
95
|
+
parser.add_argument("--head-ref", required=True)
|
|
96
|
+
parser.add_argument("--starting-sha", required=True)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--is-multi-pr",
|
|
99
|
+
action="store_true",
|
|
100
|
+
default=False,
|
|
101
|
+
)
|
|
102
|
+
return parser.parse_args(all_argv)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main(
|
|
106
|
+
all_arguments: list[str], *, is_multi_pr: bool | None = None
|
|
107
|
+
) -> int:
|
|
108
|
+
"""Entry point: create loop-state.json and print its path.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
all_arguments: Command-line arguments.
|
|
112
|
+
is_multi_pr: Override for multi-PR mode (default: from CLI).
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
0 on success.
|
|
116
|
+
"""
|
|
117
|
+
arguments = parse_arguments(all_arguments)
|
|
118
|
+
if arguments.starting_sha is None:
|
|
119
|
+
return 1
|
|
120
|
+
state_path = create_loop_state(
|
|
121
|
+
pr_number=getattr(arguments, "pr_number"),
|
|
122
|
+
head_ref=getattr(arguments, "head_ref"),
|
|
123
|
+
starting_sha=arguments.starting_sha,
|
|
124
|
+
is_multi_pr=(
|
|
125
|
+
arguments.is_multi_pr if is_multi_pr is None else is_multi_pr
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
print(state_path)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
all_argv = sys.argv[1:]
|
|
134
|
+
arguments = parse_arguments(all_argv)
|
|
135
|
+
raise SystemExit(main(all_argv, is_multi_pr=arguments.is_multi_pr))
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Remove git worktrees and run temp directories for a bugteam run.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python scripts/teardown_worktrees.py --run-temp-dir <PATH> --all-pr-jsons <JSON>
|
|
5
|
+
|
|
6
|
+
The JSON array must contain objects with keys: number, owner, repo.
|
|
7
|
+
Tolerates already-removed worktrees and missing directories.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import stat
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
_self_dir = Path(__file__).resolve().parent
|
|
23
|
+
if str(_self_dir) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(_self_dir))
|
|
25
|
+
|
|
26
|
+
from _path_resolver import per_pr_workspace
|
|
27
|
+
from config.path_resolver_constants import ALL_PYTHON_ONEXC_VERSION
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _remove_readonly_attribute(
|
|
31
|
+
removal_function: Callable[[str], None],
|
|
32
|
+
target_path: str,
|
|
33
|
+
*_exc_info: object,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Windows-safe handler: strip ReadOnly attribute and retry the syscall.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
removal_function: The syscall that failed (os.unlink or os.rmdir).
|
|
39
|
+
target_path: Path to the file or directory that triggered the error.
|
|
40
|
+
*_exc_info: Exception information (collapses onerror/onexc signature difference).
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
os.chmod(target_path, stat.S_IWRITE)
|
|
44
|
+
removal_function(target_path)
|
|
45
|
+
except OSError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def force_rmtree(target_path: str) -> None:
|
|
50
|
+
"""Remove a directory tree, handling Windows ReadOnly attribute.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
target_path: Path to the directory tree to remove.
|
|
54
|
+
"""
|
|
55
|
+
handler_kw: dict[str, object] = (
|
|
56
|
+
{"onexc": _remove_readonly_attribute}
|
|
57
|
+
if sys.version_info >= ALL_PYTHON_ONEXC_VERSION
|
|
58
|
+
else {"onerror": _remove_readonly_attribute}
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
shutil.rmtree(target_path, **handler_kw)
|
|
62
|
+
except OSError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def remove_worktree(worktree_path: Path) -> bool:
|
|
67
|
+
"""Remove a single git worktree via `git worktree remove`.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
worktree_path: Path to the worktree directory.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True when the worktree was registered and removed, False when
|
|
74
|
+
it was already absent or unregistered.
|
|
75
|
+
"""
|
|
76
|
+
if not worktree_path.exists():
|
|
77
|
+
return False
|
|
78
|
+
completed_process = subprocess.run(
|
|
79
|
+
["git", "worktree", "remove", str(worktree_path)],
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
encoding="utf-8",
|
|
83
|
+
errors="replace",
|
|
84
|
+
check=False,
|
|
85
|
+
)
|
|
86
|
+
if completed_process.returncode != 0:
|
|
87
|
+
stderr_text = completed_process.stderr or ""
|
|
88
|
+
if "not a working tree" in stderr_text.lower():
|
|
89
|
+
force_rmtree(str(worktree_path))
|
|
90
|
+
return False
|
|
91
|
+
print(
|
|
92
|
+
f"git worktree remove failed for {worktree_path}: {stderr_text}",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
return False
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def teardown_run(
|
|
100
|
+
*,
|
|
101
|
+
run_temp_dir: Path,
|
|
102
|
+
all_pr_entries: list[dict[str, object]],
|
|
103
|
+
) -> int:
|
|
104
|
+
"""Remove all worktrees and the run temp directory.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
run_temp_dir: Path to the run's temp directory.
|
|
108
|
+
all_pr_entries: List of dicts with number, owner, and repo keys.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Count of worktrees successfully removed via git.
|
|
112
|
+
"""
|
|
113
|
+
removed_count = 0
|
|
114
|
+
for each_entry in all_pr_entries:
|
|
115
|
+
pr_number = each_entry.get("number")
|
|
116
|
+
owner = each_entry.get("owner", "")
|
|
117
|
+
repo = each_entry.get("repo", "")
|
|
118
|
+
if not isinstance(pr_number, int):
|
|
119
|
+
continue
|
|
120
|
+
workspace = per_pr_workspace(run_temp_dir, str(owner), str(repo), pr_number)
|
|
121
|
+
worktree_path = workspace["worktree"]
|
|
122
|
+
if not isinstance(worktree_path, Path):
|
|
123
|
+
continue
|
|
124
|
+
if remove_worktree(worktree_path):
|
|
125
|
+
removed_count += 1
|
|
126
|
+
|
|
127
|
+
force_rmtree(str(run_temp_dir))
|
|
128
|
+
return removed_count
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
132
|
+
"""Parse command-line arguments.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
all_argv: Command-line argument list.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Parsed namespace with run_temp_dir and all_pr_jsons.
|
|
139
|
+
"""
|
|
140
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
141
|
+
parser.add_argument("--run-temp-dir", type=Path, required=True)
|
|
142
|
+
parser.add_argument("--all-pr-jsons", required=True)
|
|
143
|
+
return parser.parse_args(all_argv)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main(all_arguments: list[str]) -> int:
|
|
147
|
+
"""Entry point: remove worktrees and temp directory.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
all_arguments: Command-line arguments.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
0 on success, 1 on JSON parse failure.
|
|
154
|
+
"""
|
|
155
|
+
arguments = parse_arguments(all_arguments)
|
|
156
|
+
run_temp_dir = getattr(arguments, "run_temp_dir")
|
|
157
|
+
try:
|
|
158
|
+
all_pr_entries = json.loads(getattr(arguments, "all_pr_jsons"))
|
|
159
|
+
except json.JSONDecodeError as exc:
|
|
160
|
+
print(f"Invalid JSON for --all-pr-jsons: {exc}", file=sys.stderr)
|
|
161
|
+
return 1
|
|
162
|
+
if not isinstance(all_pr_entries, list):
|
|
163
|
+
print("--all-pr-jsons must be a JSON array", file=sys.stderr)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
removed_count = teardown_run(
|
|
167
|
+
run_temp_dir=run_temp_dir,
|
|
168
|
+
all_pr_entries=all_pr_entries,
|
|
169
|
+
)
|
|
170
|
+
print(f"Removed {removed_count} worktree(s), cleaned {run_temp_dir}")
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Validate Shape A/B contract and write <bugteam_audit> XML at the canonical path.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python scripts/write_audit_outcomes.py --pr-number 422 --loop 3 --review-url <URL> --findings-json <PATH> --worktree-path <PATH>
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from xml.etree.ElementTree import Element, SubElement
|
|
14
|
+
|
|
15
|
+
_self_dir = Path(__file__).resolve().parent
|
|
16
|
+
if str(_self_dir) not in sys.path:
|
|
17
|
+
sys.path.insert(0, str(_self_dir))
|
|
18
|
+
|
|
19
|
+
from _cli_utils import require_file
|
|
20
|
+
from _path_resolver import outcome_xml_path
|
|
21
|
+
from _xml_utils import emit_pretty_xml
|
|
22
|
+
from config.path_resolver_constants import ALL_FINDING_BODY_ELEMENT_KEYS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_audit_xml(
|
|
26
|
+
*,
|
|
27
|
+
pr_number: int,
|
|
28
|
+
loop: int,
|
|
29
|
+
review_url: str,
|
|
30
|
+
findings_json_path: Path,
|
|
31
|
+
) -> Element:
|
|
32
|
+
"""Build the <bugteam_audit> XML element from findings data.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
pr_number: Pull request number.
|
|
36
|
+
loop: Loop iteration number.
|
|
37
|
+
review_url: URL of the GitHub review.
|
|
38
|
+
findings_json_path: Path to the findings JSON file.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Root <bugteam_audit> element.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
SystemExit: When findings-json is not a JSON array of objects.
|
|
45
|
+
"""
|
|
46
|
+
findings_data = json.loads(findings_json_path.read_text(encoding="utf-8"))
|
|
47
|
+
if not isinstance(findings_data, list):
|
|
48
|
+
print("findings-json must contain a JSON array", file=sys.stderr)
|
|
49
|
+
raise SystemExit(1)
|
|
50
|
+
for each_index, each_finding in enumerate(findings_data):
|
|
51
|
+
if not isinstance(each_finding, dict):
|
|
52
|
+
print(
|
|
53
|
+
f"findings-json[{each_index}]: each entry must be a JSON object",
|
|
54
|
+
file=sys.stderr,
|
|
55
|
+
)
|
|
56
|
+
raise SystemExit(1)
|
|
57
|
+
root = Element("bugteam_audit", {
|
|
58
|
+
"pr": str(pr_number),
|
|
59
|
+
"loop": str(loop),
|
|
60
|
+
"review_url": review_url,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
findings_elem = SubElement(root, "findings")
|
|
64
|
+
_populate_findings(findings_elem, findings_data)
|
|
65
|
+
return root
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _populate_findings(parent: Element, findings_data: list[dict[str, object]]) -> None:
|
|
69
|
+
"""Populate <finding> elements from a validated list of finding dicts.
|
|
70
|
+
|
|
71
|
+
Scalar finding fields become XML attributes on `<finding>`; the
|
|
72
|
+
body fields named in `ALL_FINDING_BODY_ELEMENT_KEYS` (defined in
|
|
73
|
+
`config/path_resolver_constants.py` and currently
|
|
74
|
+
`("title", "excerpt", "description")`) become child elements.
|
|
75
|
+
Nested dicts or lists in scalar slots are flattened to string form
|
|
76
|
+
so attribute serialization stays well-defined.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
parent: Parent XML element (typically `<findings>`).
|
|
80
|
+
findings_data: Validated list of finding dicts (caller must have
|
|
81
|
+
confirmed each entry is a dict via the build_audit_xml gate).
|
|
82
|
+
"""
|
|
83
|
+
all_finding_body_element_keys = ALL_FINDING_BODY_ELEMENT_KEYS
|
|
84
|
+
for each_finding in findings_data:
|
|
85
|
+
finding_elem = SubElement(parent, "finding")
|
|
86
|
+
for each_key, each_field_detail in each_finding.items():
|
|
87
|
+
field_text = (
|
|
88
|
+
str(each_field_detail) if each_field_detail is not None else ""
|
|
89
|
+
)
|
|
90
|
+
if each_key in all_finding_body_element_keys:
|
|
91
|
+
child = SubElement(finding_elem, each_key)
|
|
92
|
+
child.text = field_text
|
|
93
|
+
else:
|
|
94
|
+
finding_elem.set(each_key, field_text)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def write_audit_xml(
|
|
98
|
+
*,
|
|
99
|
+
pr_number: int,
|
|
100
|
+
loop: int,
|
|
101
|
+
review_url: str,
|
|
102
|
+
findings_json_path: Path,
|
|
103
|
+
worktree_path: Path,
|
|
104
|
+
) -> Path:
|
|
105
|
+
"""Write the <bugteam_audit> XML to the canonical outcome path.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
pr_number: Pull request number.
|
|
109
|
+
loop: Loop iteration number.
|
|
110
|
+
review_url: URL of the GitHub review.
|
|
111
|
+
findings_json_path: Path to the findings JSON file.
|
|
112
|
+
worktree_path: Path to the git worktree.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Path to the written XML file.
|
|
116
|
+
"""
|
|
117
|
+
root = build_audit_xml(
|
|
118
|
+
pr_number=pr_number,
|
|
119
|
+
loop=loop,
|
|
120
|
+
review_url=review_url,
|
|
121
|
+
findings_json_path=findings_json_path,
|
|
122
|
+
)
|
|
123
|
+
xml_string = emit_pretty_xml(root)
|
|
124
|
+
|
|
125
|
+
output_path = outcome_xml_path(worktree_path, pr_number, loop)
|
|
126
|
+
output_path.write_text(xml_string, encoding="utf-8")
|
|
127
|
+
return output_path
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
131
|
+
"""Parse command-line arguments.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
all_argv: Command-line argument list.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Parsed namespace with pr_number, loop, review_url, findings_json, and worktree_path.
|
|
138
|
+
"""
|
|
139
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
140
|
+
parser.add_argument("--pr-number", type=int, required=True)
|
|
141
|
+
parser.add_argument("--loop", type=int, required=True)
|
|
142
|
+
parser.add_argument("--review-url", required=True)
|
|
143
|
+
parser.add_argument("--findings-json", type=Path, required=True)
|
|
144
|
+
parser.add_argument("--worktree-path", type=Path, required=True)
|
|
145
|
+
return parser.parse_args(all_argv)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main(all_arguments: list[str]) -> int:
|
|
149
|
+
"""Entry point: write <bugteam_audit> XML at the canonical path.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
all_arguments: Command-line arguments.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
0 on success, 1 on validation or write failure.
|
|
156
|
+
"""
|
|
157
|
+
arguments = parse_arguments(all_arguments)
|
|
158
|
+
findings_path = getattr(arguments, "findings_json")
|
|
159
|
+
|
|
160
|
+
early_exit = require_file(findings_path, "findings-json")
|
|
161
|
+
if early_exit is not None:
|
|
162
|
+
return early_exit
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
output_path = write_audit_xml(
|
|
166
|
+
pr_number=getattr(arguments, "pr_number"),
|
|
167
|
+
loop=arguments.loop,
|
|
168
|
+
review_url=getattr(arguments, "review_url"),
|
|
169
|
+
findings_json_path=findings_path,
|
|
170
|
+
worktree_path=getattr(arguments, "worktree_path"),
|
|
171
|
+
)
|
|
172
|
+
except SystemExit:
|
|
173
|
+
return 1
|
|
174
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
175
|
+
print(f"write_audit_xml failed: {exc}", file=sys.stderr)
|
|
176
|
+
return 1
|
|
177
|
+
print(output_path)
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Validate status enum values and write <bugteam_fix> XML at the canonical path.
|
|
2
|
+
|
|
3
|
+
Status enum (canonical source: `ALL_VALID_FIX_STATUSES` in
|
|
4
|
+
`config/path_resolver_constants.py`): fixed | could_not_address |
|
|
5
|
+
hook_blocked | unverified_fixed.
|
|
6
|
+
|
|
7
|
+
Each outcome's scalar fields become XML attributes on `<outcome>`; the
|
|
8
|
+
body fields named in `ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS` (currently
|
|
9
|
+
`("reason", "hook_output")`) become child elements.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python scripts/write_fix_outcomes.py --pr-number 422 --loop 3 --commit-sha abc1234 --outcomes-json <PATH> --worktree-path <PATH>
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from xml.etree.ElementTree import Element, SubElement
|
|
22
|
+
|
|
23
|
+
_self_dir = Path(__file__).resolve().parent
|
|
24
|
+
if str(_self_dir) not in sys.path:
|
|
25
|
+
sys.path.insert(0, str(_self_dir))
|
|
26
|
+
|
|
27
|
+
from _cli_utils import require_file
|
|
28
|
+
from _path_resolver import fix_outcome_xml_path
|
|
29
|
+
from _xml_utils import emit_pretty_xml
|
|
30
|
+
from config.path_resolver_constants import (
|
|
31
|
+
ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS,
|
|
32
|
+
ALL_VALID_FIX_STATUSES,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_fix_statuses(all_outcomes: list[dict[str, object]]) -> list[str]:
|
|
37
|
+
"""Validate that every outcome entry has a recognized status.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
all_outcomes: List of outcome dicts, each expected to have a 'status' key.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of validation error messages (empty when valid).
|
|
44
|
+
"""
|
|
45
|
+
valid_statuses = ALL_VALID_FIX_STATUSES
|
|
46
|
+
errors: list[str] = []
|
|
47
|
+
for each_index, each_outcome in enumerate(all_outcomes):
|
|
48
|
+
status = each_outcome.get("status")
|
|
49
|
+
if not isinstance(status, str):
|
|
50
|
+
errors.append(f"outcome[{each_index}]: status missing or not a string")
|
|
51
|
+
continue
|
|
52
|
+
if status not in valid_statuses:
|
|
53
|
+
errors.append(
|
|
54
|
+
f"outcome[{each_index}]: invalid status '{status}' "
|
|
55
|
+
f"(expected one of {sorted(valid_statuses)})"
|
|
56
|
+
)
|
|
57
|
+
return errors
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_fix_xml(
|
|
61
|
+
*,
|
|
62
|
+
pr_number: int,
|
|
63
|
+
loop: int,
|
|
64
|
+
commit_sha: str,
|
|
65
|
+
outcomes_json_path: Path,
|
|
66
|
+
) -> Element:
|
|
67
|
+
"""Build the <bugteam_fix> XML element from outcomes data.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
pr_number: Pull request number.
|
|
71
|
+
loop: Loop iteration number.
|
|
72
|
+
commit_sha: Commit SHA of the fix commit.
|
|
73
|
+
outcomes_json_path: Path to the outcomes JSON file.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Root <bugteam_fix> element.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
SystemExit: When outcomes-json is not a JSON array of objects, or
|
|
80
|
+
when status validation fails.
|
|
81
|
+
"""
|
|
82
|
+
outcomes_data = json.loads(outcomes_json_path.read_text(encoding="utf-8"))
|
|
83
|
+
if not isinstance(outcomes_data, list):
|
|
84
|
+
print("outcomes-json must contain a JSON array", file=sys.stderr)
|
|
85
|
+
raise SystemExit(1)
|
|
86
|
+
for each_index, each_entry in enumerate(outcomes_data):
|
|
87
|
+
if not isinstance(each_entry, dict):
|
|
88
|
+
print(
|
|
89
|
+
f"outcomes-json[{each_index}]: each entry must be a JSON object",
|
|
90
|
+
file=sys.stderr,
|
|
91
|
+
)
|
|
92
|
+
raise SystemExit(1)
|
|
93
|
+
errors = validate_fix_statuses(outcomes_data)
|
|
94
|
+
if errors:
|
|
95
|
+
for each_error in errors:
|
|
96
|
+
print(each_error, file=sys.stderr)
|
|
97
|
+
raise SystemExit(1)
|
|
98
|
+
|
|
99
|
+
root = Element("bugteam_fix", {
|
|
100
|
+
"pr": str(pr_number),
|
|
101
|
+
"loop": str(loop),
|
|
102
|
+
"commit_sha": commit_sha,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
all_fix_outcome_body_element_keys = ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS
|
|
106
|
+
outcomes_elem = SubElement(root, "outcomes")
|
|
107
|
+
for each_outcome in outcomes_data:
|
|
108
|
+
outcome_elem = SubElement(outcomes_elem, "outcome")
|
|
109
|
+
for each_key, each_field_detail in each_outcome.items():
|
|
110
|
+
field_text = (
|
|
111
|
+
str(each_field_detail) if each_field_detail is not None else ""
|
|
112
|
+
)
|
|
113
|
+
if each_key in all_fix_outcome_body_element_keys:
|
|
114
|
+
child = SubElement(outcome_elem, each_key)
|
|
115
|
+
child.text = field_text
|
|
116
|
+
else:
|
|
117
|
+
outcome_elem.set(each_key, field_text)
|
|
118
|
+
return root
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def write_fix_xml(
|
|
122
|
+
*,
|
|
123
|
+
pr_number: int,
|
|
124
|
+
loop: int,
|
|
125
|
+
commit_sha: str,
|
|
126
|
+
outcomes_json_path: Path,
|
|
127
|
+
worktree_path: Path,
|
|
128
|
+
) -> Path:
|
|
129
|
+
"""Write the <bugteam_fix> XML to the canonical outcome path.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
pr_number: Pull request number.
|
|
133
|
+
loop: Loop iteration number.
|
|
134
|
+
commit_sha: Commit SHA of the fix commit.
|
|
135
|
+
outcomes_json_path: Path to the outcomes JSON file.
|
|
136
|
+
worktree_path: Path to the git worktree.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Path to the written XML file.
|
|
140
|
+
"""
|
|
141
|
+
root = build_fix_xml(
|
|
142
|
+
pr_number=pr_number,
|
|
143
|
+
loop=loop,
|
|
144
|
+
commit_sha=commit_sha,
|
|
145
|
+
outcomes_json_path=outcomes_json_path,
|
|
146
|
+
)
|
|
147
|
+
xml_string = emit_pretty_xml(root)
|
|
148
|
+
|
|
149
|
+
output_path = fix_outcome_xml_path(worktree_path, pr_number, loop)
|
|
150
|
+
output_path.write_text(xml_string, encoding="utf-8")
|
|
151
|
+
return output_path
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
155
|
+
"""Parse command-line arguments.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
all_argv: Command-line argument list.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Parsed namespace with pr_number, loop, commit_sha, outcomes_json, and worktree_path.
|
|
162
|
+
"""
|
|
163
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
164
|
+
parser.add_argument("--pr-number", type=int, required=True)
|
|
165
|
+
parser.add_argument("--loop", type=int, required=True)
|
|
166
|
+
parser.add_argument("--commit-sha", required=True)
|
|
167
|
+
parser.add_argument("--outcomes-json", type=Path, required=True)
|
|
168
|
+
parser.add_argument("--worktree-path", type=Path, required=True)
|
|
169
|
+
return parser.parse_args(all_argv)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main(all_arguments: list[str]) -> int:
|
|
173
|
+
"""Entry point: write <bugteam_fix> XML at the canonical path.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
all_arguments: Command-line arguments.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
0 on success, 1 on validation or write failure.
|
|
180
|
+
"""
|
|
181
|
+
arguments = parse_arguments(all_arguments)
|
|
182
|
+
outcomes_path = getattr(arguments, "outcomes_json")
|
|
183
|
+
|
|
184
|
+
early_exit = require_file(outcomes_path, "outcomes-json")
|
|
185
|
+
if early_exit is not None:
|
|
186
|
+
return early_exit
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
output_path = write_fix_xml(
|
|
190
|
+
pr_number=getattr(arguments, "pr_number"),
|
|
191
|
+
loop=arguments.loop,
|
|
192
|
+
commit_sha=getattr(arguments, "commit_sha"),
|
|
193
|
+
outcomes_json_path=outcomes_path,
|
|
194
|
+
worktree_path=getattr(arguments, "worktree_path"),
|
|
195
|
+
)
|
|
196
|
+
except SystemExit:
|
|
197
|
+
return 1
|
|
198
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
199
|
+
print(f"write_fix_xml failed: {exc}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
print(output_path)
|
|
202
|
+
return 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
raise SystemExit(main(sys.argv[1:]))
|