claude-dev-env 1.95.0 → 2.0.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/_shared/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +20 -20
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +15 -12
- package/_shared/advisor/scripts/model_tier_run_validator.py +11 -10
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +25 -19
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
- package/_shared/advisor/scripts/tier_model_ids.py +18 -18
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
- package/_shared/pr-loop/worker-spawn.md +186 -0
- package/agents/code-verifier.md +1 -1
- package/bin/ever-shipped-skills.mjs +3 -0
- package/bin/expand_home_directory_tokens.mjs +1 -1
- package/bin/install.mjs +5 -2
- package/hooks/advisory/refactor_guard.py +3 -4
- package/hooks/blocking/CLAUDE.md +7 -1
- package/hooks/blocking/block_main_commit.py +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
- package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
- package/hooks/blocking/code_rules_mock_completeness.py +1 -1
- package/hooks/blocking/code_rules_optional_params.py +2 -2
- package/hooks/blocking/code_rules_shared.py +1 -1
- package/hooks/blocking/code_rules_test_assertions.py +1 -1
- package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
- package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
- package/hooks/blocking/inventory_intent_records/records.py +271 -0
- package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
- package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
- package/hooks/blocking/pii_payload_scan.py +138 -42
- package/hooks/blocking/pii_prevention_blocker.py +185 -291
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
- package/hooks/blocking/pr_description_command_parser.py +8 -4
- package/hooks/blocking/precommit_code_rules_gate.py +3 -3
- package/hooks/blocking/tdd_enforcer.py +97 -608
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
- package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
- package/hooks/blocking/test_pii_payload_scan.py +168 -0
- package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
- package/hooks/blocking/tests/conftest.py +10 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
- package/hooks/blocking/tests/test_repository_exemption.py +105 -0
- package/hooks/blocking/tests/test_repository_resolution.py +108 -0
- package/hooks/diagnostic/hook_log_extractor.py +12 -10
- package/hooks/git-hooks/post_commit.py +3 -4
- package/hooks/hooks_constants/CLAUDE.md +2 -2
- package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/local_identity.py +59 -8
- package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
- package/hooks/hooks_constants/test_local_identity.py +105 -3
- package/hooks/pyproject.toml +13 -36
- package/hooks/session/plugin_data_dir_cleanup.py +0 -1
- package/hooks/validation/mypy_validator.py +2 -2
- package/hooks/validators/health_check.py +1 -0
- package/hooks/validators/mypy_integration.py +2 -0
- package/hooks/validators/ruff_integration.py +3 -0
- package/hooks/workflow/auto_formatter.py +5 -4
- package/package.json +1 -1
- package/scripts/CLAUDE.md +4 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
- package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
- package/scripts/dev_env_scripts_constants/timing.py +7 -1
- package/scripts/grok_headless_runner.py +294 -0
- package/scripts/grok_worker_preflight.py +410 -0
- package/scripts/invoke_code_review.py +463 -0
- package/scripts/resolve_worker_spawn.py +619 -0
- package/scripts/spawn_grok_batch.py +672 -0
- package/scripts/test_grok_headless_runner.py +626 -0
- package/scripts/test_grok_worker_preflight.py +1054 -0
- package/scripts/test_invoke_code_review.py +672 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -0
- package/scripts/test_spawn_grok_batch.py +1017 -0
- package/skills/CLAUDE.md +5 -3
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +33 -11
- package/skills/autoconverge/reference/stop-conditions.md +16 -5
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +200 -16
- package/skills/bugteam/CLAUDE.md +2 -2
- package/skills/bugteam/CONSTRAINTS.md +3 -2
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +18 -13
- package/skills/bugteam/reference/audit-and-teammates.md +215 -35
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -2
- package/skills/codex-review/CLAUDE.md +46 -0
- package/skills/codex-review/SKILL.md +181 -0
- package/skills/codex-review/reference/CLAUDE.md +15 -0
- package/skills/codex-review/reference/cli-contract.md +253 -0
- package/skills/codex-review/reference/loop-integration.md +118 -0
- package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
- package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
- package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
- package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
- package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
- package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
- package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
- package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
- package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
- package/skills/codex-review/scripts/run_codex_review.py +415 -0
- package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
- package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
- package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
- package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
- package/skills/codex-review/test_skill_scaffold.py +192 -0
- package/skills/grok-spawn/CLAUDE.md +28 -0
- package/skills/grok-spawn/SKILL.md +226 -0
- package/skills/grok-spawn/reference/flag-profiles.md +132 -0
- package/skills/grok-spawn/reference/worker-briefs.md +152 -0
- package/skills/grokify/SKILL.md +9 -1
- package/skills/grokify/capability-claims.test.mjs +28 -0
- package/skills/grokify/evals/README.md +72 -0
- package/skills/grokify/evals/parse-payload.test.mjs +171 -0
- package/skills/grokify/evals/run-capability-evals.mjs +545 -0
- package/skills/orchestrator/SKILL.md +15 -11
- package/skills/orchestrator-refresh/SKILL.md +5 -5
- package/skills/pr-converge/SKILL.md +34 -13
- package/skills/pr-converge/reference/convergence-gates.md +42 -15
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +130 -42
- package/skills/pr-converge/reference/state-schema.md +10 -0
- package/skills/pr-converge/scripts/CLAUDE.md +2 -0
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
- package/skills/pr-converge/scripts/check_convergence.py +605 -29
- package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
- package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
- package/skills/pr-converge/test_step5_host_branch.py +106 -0
- package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
- package/skills/reviewer-gates/SKILL.md +7 -5
- package/skills/team-advisor/SKILL.md +7 -7
package/skills/CLAUDE.md
CHANGED
|
@@ -23,9 +23,11 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
23
23
|
|
|
24
24
|
**Planning and implementation**
|
|
25
25
|
- `anthropic-plan` — creates a source-grounded plan packet before any code changes
|
|
26
|
-
- `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage;
|
|
27
|
-
- `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage;
|
|
28
|
-
- `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent;
|
|
26
|
+
- `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage; a third-party host: max-tier Claude via CLI Claude-chain)
|
|
27
|
+
- `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; a third-party host's Claude CLI chain, no Agent-tool advisor spawn)
|
|
28
|
+
- `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable) and consults it for a second opinion before a big decision, at completion, when stuck, or when reconsidering the approach
|
|
29
|
+
- `grokify` — builds a paste-ready Grok Build handoff with a Claude advisor charter
|
|
30
|
+
- `grok-spawn` — orchestrator playbook for fleets of headless grok CLI workers (preflight, batch spec, `spawn_grok_batch.py`)
|
|
29
31
|
|
|
30
32
|
**PR review and convergence**
|
|
31
33
|
- `autoconverge` — autonomous single-run workflow that drives a PR to ready
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Builds <context> and <scope> from CLI args; <bug_categories>,
|
|
4
4
|
<rubric_reference>, and <constraints> come from the shared constants in
|
|
5
|
-
skills_pr_loop_constants; <comment_posting> and <output_format>
|
|
6
|
-
|
|
7
|
-
and stays empty when that argument is
|
|
5
|
+
skills_pr_loop_constants; <comment_posting> and <output_format> follow
|
|
6
|
+
``--flavor`` (agent or headless). <pr_description> carries the PR body
|
|
7
|
+
text read from --pr-body-file, and stays empty when that argument is
|
|
8
|
+
absent or the file cannot be read.
|
|
8
9
|
|
|
9
10
|
Usage:
|
|
10
|
-
python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH>
|
|
11
|
+
python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH> [--flavor agent|headless]
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
from __future__ import annotations
|
|
@@ -21,10 +22,18 @@ _self_dir = Path(__file__).resolve().parent
|
|
|
21
22
|
if str(_self_dir) not in sys.path:
|
|
22
23
|
sys.path.insert(0, str(_self_dir))
|
|
23
24
|
|
|
25
|
+
from _path_resolver import outcome_xml_path
|
|
24
26
|
from _xml_utils import emit_pretty_xml
|
|
25
27
|
from skills_pr_loop_constants.path_resolver_constants import (
|
|
26
28
|
ALL_AUDIT_CATEGORY_ENTRIES,
|
|
27
29
|
ALL_AUDIT_CONSTRAINT_TEXTS,
|
|
30
|
+
ALL_AUDIT_PROMPT_FLAVORS,
|
|
31
|
+
AUDIT_COMMENT_POSTING_AGENT_TEXT,
|
|
32
|
+
AUDIT_COMMENT_POSTING_HEADLESS_TEXT,
|
|
33
|
+
AUDIT_OUTPUT_FORMAT_AGENT_TEXT,
|
|
34
|
+
AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
|
|
35
|
+
AUDIT_PROMPT_FLAVOR_AGENT,
|
|
36
|
+
AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
28
37
|
AUDIT_RUBRIC_REFERENCE_TEXT,
|
|
29
38
|
)
|
|
30
39
|
|
|
@@ -61,6 +70,46 @@ def _append_pr_description(root: Element, pr_body_text: str | None) -> None:
|
|
|
61
70
|
pr_description.text = pr_body_text
|
|
62
71
|
|
|
63
72
|
|
|
73
|
+
def _comment_posting_text(flavor: str) -> str:
|
|
74
|
+
"""Return the <comment_posting> body for the given prompt flavor.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Comment-posting instruction text for that flavor.
|
|
81
|
+
"""
|
|
82
|
+
if flavor == AUDIT_PROMPT_FLAVOR_HEADLESS:
|
|
83
|
+
return AUDIT_COMMENT_POSTING_HEADLESS_TEXT
|
|
84
|
+
return AUDIT_COMMENT_POSTING_AGENT_TEXT
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _format_instruction_text(
|
|
88
|
+
*,
|
|
89
|
+
flavor: str,
|
|
90
|
+
worktree_path: Path,
|
|
91
|
+
pr_number: int,
|
|
92
|
+
loop: int,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Return the <output_format> body for the given prompt flavor.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
98
|
+
worktree_path: Path to the git worktree.
|
|
99
|
+
pr_number: Pull request number.
|
|
100
|
+
loop: Loop iteration number.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Format-instruction text for that flavor.
|
|
104
|
+
"""
|
|
105
|
+
if flavor == AUDIT_PROMPT_FLAVOR_HEADLESS:
|
|
106
|
+
outcome_path = outcome_xml_path(worktree_path, pr_number, loop)
|
|
107
|
+
return AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
|
|
108
|
+
outcome_path=outcome_path.as_posix(),
|
|
109
|
+
)
|
|
110
|
+
return AUDIT_OUTPUT_FORMAT_AGENT_TEXT
|
|
111
|
+
|
|
112
|
+
|
|
64
113
|
def build_audit_prompt_xml(
|
|
65
114
|
*,
|
|
66
115
|
owner: str,
|
|
@@ -72,6 +121,7 @@ def build_audit_prompt_xml(
|
|
|
72
121
|
worktree_path: Path,
|
|
73
122
|
run_temp_dir: Path,
|
|
74
123
|
pr_body_text: str | None = None,
|
|
124
|
+
flavor: str = AUDIT_PROMPT_FLAVOR_AGENT,
|
|
75
125
|
) -> Element:
|
|
76
126
|
"""Build the complete AUDIT spawn prompt XML.
|
|
77
127
|
|
|
@@ -85,6 +135,8 @@ def build_audit_prompt_xml(
|
|
|
85
135
|
worktree_path: Path to the git worktree.
|
|
86
136
|
run_temp_dir: Path to the run temp directory.
|
|
87
137
|
pr_body_text: PR description body text, or None when unavailable.
|
|
138
|
+
flavor: Prompt flavor — ``agent`` (MCP posting) or ``headless``
|
|
139
|
+
(outcome file, lead-owned posting).
|
|
88
140
|
|
|
89
141
|
Returns:
|
|
90
142
|
Root <spawn_prompt> element.
|
|
@@ -122,16 +174,14 @@ def build_audit_prompt_xml(
|
|
|
122
174
|
SubElement(constraints, "constraint").text = each_constraint
|
|
123
175
|
|
|
124
176
|
comment_posting = SubElement(root, "comment_posting")
|
|
125
|
-
comment_posting.text = (
|
|
126
|
-
"Post findings as inline review comments on the PR via "
|
|
127
|
-
"the GitHub MCP add_comment_to_pending_review tool. "
|
|
128
|
-
"Group related findings into a single pending review."
|
|
129
|
-
)
|
|
177
|
+
comment_posting.text = _comment_posting_text(flavor)
|
|
130
178
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
179
|
+
format_section = SubElement(root, "output_format")
|
|
180
|
+
format_section.text = _format_instruction_text(
|
|
181
|
+
flavor=flavor,
|
|
182
|
+
worktree_path=worktree_path,
|
|
183
|
+
pr_number=pr_number,
|
|
184
|
+
loop=loop,
|
|
135
185
|
)
|
|
136
186
|
|
|
137
187
|
return root
|
|
@@ -148,6 +198,7 @@ def emit_audit_prompt(
|
|
|
148
198
|
worktree_path: Path,
|
|
149
199
|
run_temp_dir: Path,
|
|
150
200
|
pr_body_text: str | None = None,
|
|
201
|
+
flavor: str = AUDIT_PROMPT_FLAVOR_AGENT,
|
|
151
202
|
) -> str:
|
|
152
203
|
"""Build and serialize the AUDIT spawn prompt to a pretty-printed XML string.
|
|
153
204
|
|
|
@@ -161,6 +212,7 @@ def emit_audit_prompt(
|
|
|
161
212
|
worktree_path: Path to the git worktree.
|
|
162
213
|
run_temp_dir: Path to the run temp directory.
|
|
163
214
|
pr_body_text: PR description body text, or None when unavailable.
|
|
215
|
+
flavor: Prompt flavor — ``agent`` or ``headless``.
|
|
164
216
|
|
|
165
217
|
Returns:
|
|
166
218
|
Pretty-printed XML string.
|
|
@@ -175,6 +227,7 @@ def emit_audit_prompt(
|
|
|
175
227
|
worktree_path=worktree_path,
|
|
176
228
|
run_temp_dir=run_temp_dir,
|
|
177
229
|
pr_body_text=pr_body_text,
|
|
230
|
+
flavor=flavor,
|
|
178
231
|
)
|
|
179
232
|
return emit_pretty_xml(root)
|
|
180
233
|
|
|
@@ -198,6 +251,11 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
|
198
251
|
parser.add_argument("--worktree-path", type=Path, required=True)
|
|
199
252
|
parser.add_argument("--run-temp-dir", type=Path, required=True)
|
|
200
253
|
parser.add_argument("--pr-body-file", type=Path, default=None)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--flavor",
|
|
256
|
+
choices=sorted(ALL_AUDIT_PROMPT_FLAVORS),
|
|
257
|
+
default=AUDIT_PROMPT_FLAVOR_AGENT,
|
|
258
|
+
)
|
|
201
259
|
return parser.parse_args(all_argv)
|
|
202
260
|
|
|
203
261
|
|
|
@@ -221,6 +279,7 @@ def main(all_arguments: list[str]) -> int:
|
|
|
221
279
|
worktree_path=arguments.worktree_path,
|
|
222
280
|
run_temp_dir=arguments.run_temp_dir,
|
|
223
281
|
pr_body_text=read_pr_body_text(arguments.pr_body_file),
|
|
282
|
+
flavor=arguments.flavor,
|
|
224
283
|
)
|
|
225
284
|
sys.stdout.write(xml_output)
|
|
226
285
|
return 0
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Emit the complete FIX spawn prompt XML to stdout.
|
|
2
2
|
|
|
3
3
|
Populates <bug> entries from findings JSON, plus <execution> checklist
|
|
4
|
-
and <constraints>.
|
|
4
|
+
and <constraints>. <comment_posting> and <output_format> follow
|
|
5
|
+
``--flavor`` (agent or headless).
|
|
5
6
|
|
|
6
7
|
Usage:
|
|
7
|
-
python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --findings-json <PATH>
|
|
8
|
+
python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --findings-json <PATH> [--flavor agent|headless]
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
@@ -20,13 +21,92 @@ if str(_self_dir) not in sys.path:
|
|
|
20
21
|
sys.path.insert(0, str(_self_dir))
|
|
21
22
|
|
|
22
23
|
from _cli_utils import require_file
|
|
24
|
+
from _path_resolver import fix_outcome_xml_path
|
|
23
25
|
from _xml_utils import emit_pretty_xml
|
|
24
26
|
from skills_pr_loop_constants.path_resolver_constants import (
|
|
25
27
|
ALL_FIX_CONSTRAINT_TEXTS,
|
|
28
|
+
ALL_FIX_CONSTRAINT_TEXTS_HEADLESS,
|
|
26
29
|
ALL_FIX_EXECUTION_STEPS,
|
|
30
|
+
ALL_FIX_EXECUTION_STEPS_HEADLESS,
|
|
31
|
+
ALL_FIX_PROMPT_FLAVORS,
|
|
32
|
+
FINDINGS_JSON_MUST_BE_LIST_MESSAGE,
|
|
33
|
+
FIX_COMMENT_POSTING_AGENT_TEXT,
|
|
34
|
+
FIX_COMMENT_POSTING_HEADLESS_TEXT,
|
|
35
|
+
FIX_OUTPUT_FORMAT_AGENT_TEXT,
|
|
36
|
+
FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
|
|
37
|
+
FIX_PROMPT_FLAVOR_AGENT,
|
|
38
|
+
FIX_PROMPT_FLAVOR_HEADLESS,
|
|
27
39
|
)
|
|
28
40
|
|
|
29
41
|
|
|
42
|
+
def _comment_posting_text(flavor: str) -> str:
|
|
43
|
+
"""Return the <comment_posting> body for the given prompt flavor.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Comment-posting instruction text for that flavor.
|
|
50
|
+
"""
|
|
51
|
+
if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
|
|
52
|
+
return FIX_COMMENT_POSTING_HEADLESS_TEXT
|
|
53
|
+
return FIX_COMMENT_POSTING_AGENT_TEXT
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _format_instruction_text(
|
|
57
|
+
*,
|
|
58
|
+
flavor: str,
|
|
59
|
+
worktree_path: Path,
|
|
60
|
+
pr_number: int,
|
|
61
|
+
loop: int,
|
|
62
|
+
) -> str:
|
|
63
|
+
"""Return the <output_format> body for the given prompt flavor.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
67
|
+
worktree_path: Path to the git worktree.
|
|
68
|
+
pr_number: Pull request number.
|
|
69
|
+
loop: Loop iteration number.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Format-instruction text for that flavor.
|
|
73
|
+
"""
|
|
74
|
+
if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
|
|
75
|
+
outcome_path = fix_outcome_xml_path(worktree_path, pr_number, loop)
|
|
76
|
+
return FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
|
|
77
|
+
outcome_path=outcome_path.as_posix(),
|
|
78
|
+
)
|
|
79
|
+
return FIX_OUTPUT_FORMAT_AGENT_TEXT
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _all_execution_steps(flavor: str) -> list[str]:
|
|
83
|
+
"""Return the execution checklist for the given prompt flavor.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Ordered execution step texts for that flavor.
|
|
90
|
+
"""
|
|
91
|
+
if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
|
|
92
|
+
return list(ALL_FIX_EXECUTION_STEPS_HEADLESS)
|
|
93
|
+
return list(ALL_FIX_EXECUTION_STEPS)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _all_constraint_texts(flavor: str) -> list[str]:
|
|
97
|
+
"""Return the constraint texts for the given prompt flavor.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
flavor: ``agent`` or ``headless`` prompt flavor.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Ordered constraint texts for that flavor.
|
|
104
|
+
"""
|
|
105
|
+
if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
|
|
106
|
+
return list(ALL_FIX_CONSTRAINT_TEXTS_HEADLESS)
|
|
107
|
+
return list(ALL_FIX_CONSTRAINT_TEXTS)
|
|
108
|
+
|
|
109
|
+
|
|
30
110
|
def build_fix_prompt_xml(
|
|
31
111
|
*,
|
|
32
112
|
owner: str,
|
|
@@ -37,6 +117,7 @@ def build_fix_prompt_xml(
|
|
|
37
117
|
base_ref: str,
|
|
38
118
|
worktree_path: Path,
|
|
39
119
|
findings_json_path: Path,
|
|
120
|
+
flavor: str = FIX_PROMPT_FLAVOR_AGENT,
|
|
40
121
|
) -> Element:
|
|
41
122
|
"""Build the complete FIX spawn prompt XML.
|
|
42
123
|
|
|
@@ -49,11 +130,18 @@ def build_fix_prompt_xml(
|
|
|
49
130
|
base_ref: Base branch ref.
|
|
50
131
|
worktree_path: Path to the git worktree.
|
|
51
132
|
findings_json_path: Path to the findings JSON file.
|
|
133
|
+
flavor: Prompt flavor — ``agent`` (in-agent commit/post) or
|
|
134
|
+
``headless`` (outcome file, lead-owned commit/post).
|
|
52
135
|
|
|
53
136
|
Returns:
|
|
54
137
|
Root <spawn_prompt> element.
|
|
55
138
|
"""
|
|
56
139
|
findings_data = json.loads(findings_json_path.read_text(encoding="utf-8"))
|
|
140
|
+
if not isinstance(findings_data, list):
|
|
141
|
+
findings_type_name = type(findings_data).__name__
|
|
142
|
+
raise TypeError(
|
|
143
|
+
FINDINGS_JSON_MUST_BE_LIST_MESSAGE % findings_type_name
|
|
144
|
+
)
|
|
57
145
|
|
|
58
146
|
root = Element("spawn_prompt", {"role": "fix", "loop": str(loop)})
|
|
59
147
|
|
|
@@ -66,24 +154,34 @@ def build_fix_prompt_xml(
|
|
|
66
154
|
SubElement(context, "worktree_path").text = worktree_path.as_posix()
|
|
67
155
|
|
|
68
156
|
bugs_elem = SubElement(root, "bugs")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
157
|
+
for each_finding in findings_data:
|
|
158
|
+
if isinstance(each_finding, dict):
|
|
159
|
+
bug = SubElement(bugs_elem, "bug")
|
|
160
|
+
for each_key, each_field_detail in each_finding.items():
|
|
161
|
+
child = SubElement(bug, each_key)
|
|
162
|
+
child.text = (
|
|
163
|
+
str(each_field_detail) if each_field_detail is not None else ""
|
|
164
|
+
)
|
|
78
165
|
|
|
79
166
|
execution = SubElement(root, "execution")
|
|
80
|
-
for each_step in
|
|
167
|
+
for each_step in _all_execution_steps(flavor):
|
|
81
168
|
SubElement(execution, "step").text = each_step
|
|
82
169
|
|
|
83
170
|
constraints = SubElement(root, "constraints")
|
|
84
|
-
for each_constraint in
|
|
171
|
+
for each_constraint in _all_constraint_texts(flavor):
|
|
85
172
|
SubElement(constraints, "constraint").text = each_constraint
|
|
86
173
|
|
|
174
|
+
comment_posting = SubElement(root, "comment_posting")
|
|
175
|
+
comment_posting.text = _comment_posting_text(flavor)
|
|
176
|
+
|
|
177
|
+
format_section = SubElement(root, "output_format")
|
|
178
|
+
format_section.text = _format_instruction_text(
|
|
179
|
+
flavor=flavor,
|
|
180
|
+
worktree_path=worktree_path,
|
|
181
|
+
pr_number=pr_number,
|
|
182
|
+
loop=loop,
|
|
183
|
+
)
|
|
184
|
+
|
|
87
185
|
return root
|
|
88
186
|
|
|
89
187
|
|
|
@@ -97,6 +195,7 @@ def emit_fix_prompt(
|
|
|
97
195
|
base_ref: str,
|
|
98
196
|
worktree_path: Path,
|
|
99
197
|
findings_json_path: Path,
|
|
198
|
+
flavor: str = FIX_PROMPT_FLAVOR_AGENT,
|
|
100
199
|
) -> str:
|
|
101
200
|
"""Build and serialize the FIX spawn prompt to a pretty-printed XML string.
|
|
102
201
|
|
|
@@ -109,6 +208,7 @@ def emit_fix_prompt(
|
|
|
109
208
|
base_ref: Base branch ref.
|
|
110
209
|
worktree_path: Path to the git worktree.
|
|
111
210
|
findings_json_path: Path to the findings JSON file.
|
|
211
|
+
flavor: Prompt flavor — ``agent`` or ``headless``.
|
|
112
212
|
|
|
113
213
|
Returns:
|
|
114
214
|
Pretty-printed XML string.
|
|
@@ -122,6 +222,7 @@ def emit_fix_prompt(
|
|
|
122
222
|
base_ref=base_ref,
|
|
123
223
|
worktree_path=worktree_path,
|
|
124
224
|
findings_json_path=findings_json_path,
|
|
225
|
+
flavor=flavor,
|
|
125
226
|
)
|
|
126
227
|
return emit_pretty_xml(root)
|
|
127
228
|
|
|
@@ -144,6 +245,11 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
|
144
245
|
parser.add_argument("--base-ref", required=True)
|
|
145
246
|
parser.add_argument("--worktree-path", type=Path, required=True)
|
|
146
247
|
parser.add_argument("--findings-json", type=Path, required=True)
|
|
248
|
+
parser.add_argument(
|
|
249
|
+
"--flavor",
|
|
250
|
+
choices=sorted(ALL_FIX_PROMPT_FLAVORS),
|
|
251
|
+
default=FIX_PROMPT_FLAVOR_AGENT,
|
|
252
|
+
)
|
|
147
253
|
return parser.parse_args(all_argv)
|
|
148
254
|
|
|
149
255
|
|
|
@@ -173,8 +279,9 @@ def main(all_arguments: list[str]) -> int:
|
|
|
173
279
|
base_ref=getattr(arguments, "base_ref"),
|
|
174
280
|
worktree_path=getattr(arguments, "worktree_path"),
|
|
175
281
|
findings_json_path=findings_path,
|
|
282
|
+
flavor=arguments.flavor,
|
|
176
283
|
)
|
|
177
|
-
except (json.JSONDecodeError, OSError) as exc:
|
|
284
|
+
except (json.JSONDecodeError, OSError, TypeError) as exc:
|
|
178
285
|
print(f"emit_fix_prompt failed: {exc}", file=sys.stderr)
|
|
179
286
|
return 1
|
|
180
287
|
sys.stdout.write(xml_output)
|
|
@@ -56,6 +56,34 @@ AUDIT_RUBRIC_REFERENCE_TEXT = (
|
|
|
56
56
|
"Read the rubric files before auditing."
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
+
AUDIT_PROMPT_FLAVOR_AGENT = "agent"
|
|
60
|
+
AUDIT_PROMPT_FLAVOR_HEADLESS = "headless"
|
|
61
|
+
ALL_AUDIT_PROMPT_FLAVORS = frozenset({
|
|
62
|
+
AUDIT_PROMPT_FLAVOR_AGENT,
|
|
63
|
+
AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
AUDIT_COMMENT_POSTING_AGENT_TEXT = (
|
|
67
|
+
"Post findings as inline review comments on the PR via "
|
|
68
|
+
"the GitHub MCP add_comment_to_pending_review tool. "
|
|
69
|
+
"Group related findings into a single pending review."
|
|
70
|
+
)
|
|
71
|
+
AUDIT_COMMENT_POSTING_HEADLESS_TEXT = (
|
|
72
|
+
"Do not post reviews or comments. Use gh only when you must read "
|
|
73
|
+
"PR metadata. Write findings into the outcome XML named in "
|
|
74
|
+
"<output_format>. The lead session posts the review with "
|
|
75
|
+
"post_audit_thread.py after the worker exits."
|
|
76
|
+
)
|
|
77
|
+
AUDIT_OUTPUT_FORMAT_AGENT_TEXT = (
|
|
78
|
+
"Emit findings as JSON array of objects with keys: "
|
|
79
|
+
"severity (P0/P1/P2), file, line, category, message, suggestion."
|
|
80
|
+
)
|
|
81
|
+
AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE = (
|
|
82
|
+
"Write the complete audit outcome XML to {outcome_path}. "
|
|
83
|
+
"Leave review_url empty when the lead posts after you exit. "
|
|
84
|
+
"Do not use TaskCreate, MCP tools, or Artifact tools."
|
|
85
|
+
)
|
|
86
|
+
|
|
59
87
|
ALL_FIX_EXECUTION_STEPS = [
|
|
60
88
|
"Read the finding and verify it against the current file at file:line.",
|
|
61
89
|
"Write a failing test that reproduces the bug.",
|
|
@@ -66,6 +94,14 @@ ALL_FIX_EXECUTION_STEPS = [
|
|
|
66
94
|
"Post an inline reply on the finding thread confirming the fix.",
|
|
67
95
|
]
|
|
68
96
|
|
|
97
|
+
ALL_FIX_EXECUTION_STEPS_HEADLESS = [
|
|
98
|
+
"Read the finding and verify it against the current file at file:line.",
|
|
99
|
+
"Write a failing test that reproduces the bug.",
|
|
100
|
+
"Implement the smallest change that resolves the finding.",
|
|
101
|
+
"Run the full test suite — confirm the new test and all existing tests pass.",
|
|
102
|
+
"Leave the working tree dirty with your edits. Do not stage, commit, or push.",
|
|
103
|
+
]
|
|
104
|
+
|
|
69
105
|
ALL_FIX_CONSTRAINT_TEXTS = [
|
|
70
106
|
"Work exclusively within the worktree directory.",
|
|
71
107
|
"Change only the lines directly related to each finding.",
|
|
@@ -77,8 +113,50 @@ ALL_FIX_CONSTRAINT_TEXTS = [
|
|
|
77
113
|
"forward slashes (e.g. C:/Users/...), even on Windows.",
|
|
78
114
|
]
|
|
79
115
|
|
|
116
|
+
ALL_FIX_CONSTRAINT_TEXTS_HEADLESS = [
|
|
117
|
+
"Work exclusively within the worktree directory.",
|
|
118
|
+
"Change only the lines directly related to each finding.",
|
|
119
|
+
"Every fix must have a corresponding test.",
|
|
120
|
+
"Remove deprecated code directly and update all call sites.",
|
|
121
|
+
"Handle each error case with a named exception type.",
|
|
122
|
+
"Do not stage, commit, or push. The lead owns git write operations.",
|
|
123
|
+
"Do not use TaskCreate, MCP tools, or Artifact tools.",
|
|
124
|
+
"Double-quote every path in shell commands and write paths with "
|
|
125
|
+
"forward slashes (e.g. C:/Users/...), even on Windows.",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
FIX_PROMPT_FLAVOR_AGENT = "agent"
|
|
129
|
+
FIX_PROMPT_FLAVOR_HEADLESS = "headless"
|
|
130
|
+
ALL_FIX_PROMPT_FLAVORS = frozenset({
|
|
131
|
+
FIX_PROMPT_FLAVOR_AGENT,
|
|
132
|
+
FIX_PROMPT_FLAVOR_HEADLESS,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
FIX_COMMENT_POSTING_AGENT_TEXT = (
|
|
136
|
+
"After commit, post one reply per finding thread via the GitHub MCP "
|
|
137
|
+
"add_reply_to_pull_request_comment tool, then resolve the thread."
|
|
138
|
+
)
|
|
139
|
+
FIX_COMMENT_POSTING_HEADLESS_TEXT = (
|
|
140
|
+
"Do not post replies or resolve threads. Use gh only when you must read "
|
|
141
|
+
"PR metadata. Write per-finding outcomes into the outcome XML named in "
|
|
142
|
+
"<output_format>. The lead session posts replies after the worker exits."
|
|
143
|
+
)
|
|
144
|
+
FIX_OUTPUT_FORMAT_AGENT_TEXT = (
|
|
145
|
+
"After posting replies, write the complete fix outcome XML "
|
|
146
|
+
"(schema in PROMPTS.md)."
|
|
147
|
+
)
|
|
148
|
+
FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE = (
|
|
149
|
+
"Write the complete fix outcome XML to {outcome_path}. "
|
|
150
|
+
"Leave commit_sha empty when the lead commits after you exit. "
|
|
151
|
+
"Do not use TaskCreate, MCP tools, or Artifact tools."
|
|
152
|
+
)
|
|
153
|
+
|
|
80
154
|
ALL_PYTHON_ONEXC_VERSION = (3, 12)
|
|
81
155
|
|
|
156
|
+
FINDINGS_JSON_MUST_BE_LIST_MESSAGE = (
|
|
157
|
+
"findings-json root must be a JSON array of finding objects, got %s"
|
|
158
|
+
)
|
|
159
|
+
|
|
82
160
|
ALL_FINDING_BODY_ELEMENT_KEYS: tuple[str, ...] = (
|
|
83
161
|
"title",
|
|
84
162
|
"excerpt",
|
|
@@ -9,12 +9,21 @@ from pathlib import Path
|
|
|
9
9
|
from types import ModuleType
|
|
10
10
|
from xml.etree.ElementTree import Element
|
|
11
11
|
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
12
14
|
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
13
15
|
if str(_SCRIPTS_DIR) not in sys.path:
|
|
14
16
|
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
15
17
|
|
|
16
18
|
from skills_pr_loop_constants.path_resolver_constants import (
|
|
17
19
|
ALL_AUDIT_CATEGORY_ENTRIES,
|
|
20
|
+
AUDIT_COMMENT_POSTING_AGENT_TEXT,
|
|
21
|
+
AUDIT_COMMENT_POSTING_HEADLESS_TEXT,
|
|
22
|
+
AUDIT_OUTPUT_FORMAT_AGENT_TEXT,
|
|
23
|
+
AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
|
|
24
|
+
AUDIT_PROMPT_FLAVOR_AGENT,
|
|
25
|
+
AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
26
|
+
OUTCOME_XML_TEMPLATE,
|
|
18
27
|
)
|
|
19
28
|
|
|
20
29
|
_CATEGORY_RUBRICS_DIR = _SCRIPTS_DIR.parents[3] / "audit-rubrics" / "category_rubrics"
|
|
@@ -257,3 +266,115 @@ def test_parse_arguments_reads_pr_body_file_path() -> None:
|
|
|
257
266
|
"--pr-body-file", "/tmp/body.md",
|
|
258
267
|
])
|
|
259
268
|
assert arguments.pr_body_file == Path("/tmp/body.md")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_parse_arguments_defaults_flavor_to_agent() -> None:
|
|
272
|
+
arguments = build_audit_prompt.parse_arguments([
|
|
273
|
+
"--owner", "jl-cmd",
|
|
274
|
+
"--repo", "claude-dev-env",
|
|
275
|
+
"--pr-number", "422",
|
|
276
|
+
"--loop", "1",
|
|
277
|
+
"--head-ref", "feat/branch",
|
|
278
|
+
"--base-ref", "main",
|
|
279
|
+
"--worktree-path", "/tmp/wt",
|
|
280
|
+
"--run-temp-dir", "/tmp/rt",
|
|
281
|
+
])
|
|
282
|
+
assert arguments.flavor == AUDIT_PROMPT_FLAVOR_AGENT
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_parse_arguments_reads_headless_flavor() -> None:
|
|
286
|
+
arguments = build_audit_prompt.parse_arguments([
|
|
287
|
+
"--owner", "jl-cmd",
|
|
288
|
+
"--repo", "claude-dev-env",
|
|
289
|
+
"--pr-number", "422",
|
|
290
|
+
"--loop", "1",
|
|
291
|
+
"--head-ref", "feat/branch",
|
|
292
|
+
"--base-ref", "main",
|
|
293
|
+
"--worktree-path", "/tmp/wt",
|
|
294
|
+
"--run-temp-dir", "/tmp/rt",
|
|
295
|
+
"--flavor", AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
296
|
+
])
|
|
297
|
+
assert arguments.flavor == AUDIT_PROMPT_FLAVOR_HEADLESS
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_agent_flavor_comment_posting_names_mcp_tool() -> None:
|
|
301
|
+
root = build_audit_prompt.build_audit_prompt_xml(
|
|
302
|
+
owner="jl-cmd",
|
|
303
|
+
repo="claude-dev-env",
|
|
304
|
+
pr_number=422,
|
|
305
|
+
loop=1,
|
|
306
|
+
head_ref="feat/branch",
|
|
307
|
+
base_ref="main",
|
|
308
|
+
worktree_path=Path("/tmp/bugteam-pr-422/worktree"),
|
|
309
|
+
run_temp_dir=Path("/tmp/bugteam-pr-422"),
|
|
310
|
+
flavor=AUDIT_PROMPT_FLAVOR_AGENT,
|
|
311
|
+
)
|
|
312
|
+
comment_posting = root.find("comment_posting")
|
|
313
|
+
assert comment_posting is not None
|
|
314
|
+
assert comment_posting.text == AUDIT_COMMENT_POSTING_AGENT_TEXT
|
|
315
|
+
output_format = root.find("output_format")
|
|
316
|
+
assert output_format is not None
|
|
317
|
+
assert output_format.text == AUDIT_OUTPUT_FORMAT_AGENT_TEXT
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_headless_flavor_writes_outcome_path_and_forbids_mcp() -> None:
|
|
321
|
+
worktree_path = Path("/tmp/bugteam-pr-422/worktree")
|
|
322
|
+
pr_number = 422
|
|
323
|
+
loop = 3
|
|
324
|
+
root = build_audit_prompt.build_audit_prompt_xml(
|
|
325
|
+
owner="jl-cmd",
|
|
326
|
+
repo="claude-dev-env",
|
|
327
|
+
pr_number=pr_number,
|
|
328
|
+
loop=loop,
|
|
329
|
+
head_ref="feat/branch",
|
|
330
|
+
base_ref="main",
|
|
331
|
+
worktree_path=worktree_path,
|
|
332
|
+
run_temp_dir=Path("/tmp/bugteam-pr-422"),
|
|
333
|
+
flavor=AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
334
|
+
)
|
|
335
|
+
comment_posting = root.find("comment_posting")
|
|
336
|
+
assert comment_posting is not None
|
|
337
|
+
assert comment_posting.text == AUDIT_COMMENT_POSTING_HEADLESS_TEXT
|
|
338
|
+
assert "MCP" not in (comment_posting.text or "")
|
|
339
|
+
assert "TaskCreate" not in (comment_posting.text or "")
|
|
340
|
+
assert "gh" in (comment_posting.text or "")
|
|
341
|
+
assert "post_audit_thread.py" in (comment_posting.text or "")
|
|
342
|
+
|
|
343
|
+
expected_outcome_path = (
|
|
344
|
+
worktree_path / OUTCOME_XML_TEMPLATE.format(number=pr_number, loop=loop)
|
|
345
|
+
).as_posix()
|
|
346
|
+
output_format = root.find("output_format")
|
|
347
|
+
assert output_format is not None
|
|
348
|
+
assert output_format.text == AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
|
|
349
|
+
outcome_path=expected_outcome_path,
|
|
350
|
+
)
|
|
351
|
+
assert expected_outcome_path in (output_format.text or "")
|
|
352
|
+
assert "TaskCreate" in (output_format.text or "")
|
|
353
|
+
assert "MCP" in (output_format.text or "")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_main_headless_flavor_emits_outcome_path_on_stdout(
|
|
357
|
+
capsys: pytest.CaptureFixture[str],
|
|
358
|
+
) -> None:
|
|
359
|
+
worktree_path = "/tmp/bugteam-pr-99/worktree"
|
|
360
|
+
exit_code = build_audit_prompt.main([
|
|
361
|
+
"--owner", "jl-cmd",
|
|
362
|
+
"--repo", "claude-dev-env",
|
|
363
|
+
"--pr-number", "99",
|
|
364
|
+
"--loop", "2",
|
|
365
|
+
"--head-ref", "feat/branch",
|
|
366
|
+
"--base-ref", "main",
|
|
367
|
+
"--worktree-path", worktree_path,
|
|
368
|
+
"--run-temp-dir", "/tmp/bugteam-pr-99",
|
|
369
|
+
"--flavor", AUDIT_PROMPT_FLAVOR_HEADLESS,
|
|
370
|
+
])
|
|
371
|
+
assert exit_code == 0
|
|
372
|
+
captured = capsys.readouterr()
|
|
373
|
+
expected_outcome = (
|
|
374
|
+
Path(worktree_path)
|
|
375
|
+
/ OUTCOME_XML_TEMPLATE.format(number=99, loop=2)
|
|
376
|
+
).as_posix()
|
|
377
|
+
assert expected_outcome in captured.out
|
|
378
|
+
assert "post_audit_thread.py" in captured.out
|
|
379
|
+
assert "Do not post reviews" in captured.out
|
|
380
|
+
assert "add_comment_to_pending_review" not in captured.out
|