claude-dev-env 1.92.1 → 1.93.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 +6 -2
- package/_shared/CLAUDE.md +2 -1
- package/_shared/advisor/CLAUDE.md +21 -0
- package/_shared/advisor/advisor-protocol.md +142 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
- package/_shared/advisor/scripts/pyproject.toml +3 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
- package/_shared/advisor/scripts/tier_model_ids.py +164 -0
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/precatch-rubric.md +65 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
- package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
- package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
- package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
- package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
- package/agents/CLAUDE.md +3 -2
- package/agents/clasp-deployment-orchestrator.md +2 -2
- package/agents/code-advisor.md +7 -5
- package/agents/code-verifier.md +6 -2
- package/agents/session-advisor.md +27 -0
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
- package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
- package/bin/CLAUDE.md +1 -0
- package/bin/expand_home_directory_tokens.mjs +75 -0
- package/bin/install.mjs +21 -7
- package/bin/install.test.mjs +220 -10
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +4 -1
- package/hooks/blocking/_path_setup.py +13 -0
- package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/code_rules_imports_logging.py +34 -1
- package/hooks/blocking/code_rules_paths_syspath.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +272 -0
- package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
- package/hooks/blocking/pii_prevention_blocker.py +781 -0
- package/hooks/blocking/pii_scanner.py +340 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/stop_dispatcher.py +84 -0
- package/hooks/blocking/test__path_setup.py +39 -0
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
- package/hooks/blocking/test_destructive_command_blocker.py +29 -9
- package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
- package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
- package/hooks/blocking/test_pii_scanner.py +165 -0
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
- package/hooks/blocking/test_stop_dispatcher.py +187 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
- package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
- package/hooks/diagnostic/migrations/README.md +25 -26
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
- package/hooks/hooks.json +10 -120
- package/hooks/hooks_constants/CLAUDE.md +12 -3
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
- package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
- package/hooks/hooks_constants/local_identity.py +182 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
- package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
- package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
- package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
- package/hooks/hooks_constants/test_layout_constants.py +28 -0
- package/hooks/hooks_constants/test_local_identity.py +88 -0
- package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
- package/hooks/validators/README.md +5 -6
- package/hooks/validators/__init__.py +2 -2
- package/hooks/validators/python_style_checks.py +298 -243
- package/hooks/validators/run_all_validators.py +6 -0
- package/hooks/validators/test_python_style_checks.py +278 -163
- package/package.json +2 -2
- package/rules/CLAUDE.md +2 -1
- package/rules/bdd.md +1 -1
- package/rules/nas-ssh-invocation.md +6 -4
- package/rules/no-justification-noise.md +61 -0
- package/scripts/test_setup_project_paths.py +1 -1
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +113 -35
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +44 -22
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/reference/stop-conditions.md +23 -13
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
- package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.mjs +329 -68
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
- package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
- package/skills/bdd-protocol/SKILL.md +4 -5
- package/skills/bdd-protocol/references/anti-patterns.md +1 -1
- package/skills/bugteam/SKILL.md +8 -0
- package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +7 -5
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/copilot-finding-triage/SKILL.md +124 -0
- package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
- package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
- package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
- package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
- package/skills/copilot-finding-triage/templates/notification.md +34 -0
- package/skills/copilot-review/SKILL.md +8 -6
- package/skills/findbugs/SKILL.md +4 -0
- package/skills/fixbugs/SKILL.md +8 -7
- package/skills/gotcha/CLAUDE.md +2 -2
- package/skills/gotcha/SKILL.md +4 -4
- package/skills/log-audit/SKILL.md +4 -6
- package/skills/monitor-open-prs/CLAUDE.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -1
- package/skills/orchestrator/SKILL.md +74 -78
- package/skills/orchestrator-refresh/SKILL.md +24 -12
- package/skills/post-audit-findings/SKILL.md +5 -9
- package/skills/pr-consistency-audit/SKILL.md +5 -1
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +86 -47
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +20 -17
- package/skills/pr-converge/reference/examples.md +63 -47
- package/skills/pr-converge/reference/fix-protocol.md +15 -15
- package/skills/pr-converge/reference/ground-rules.md +11 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
- package/skills/pr-converge/reference/per-tick.md +129 -107
- package/skills/pr-converge/reference/state-schema.md +15 -10
- package/skills/pr-converge/scripts/check_convergence.py +5 -4
- package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
- package/skills/pr-fix-protocol/SKILL.md +3 -8
- package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
- package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
- package/skills/pr-loop-lifecycle/SKILL.md +5 -9
- package/skills/pr-scope-resolve/SKILL.md +3 -6
- package/skills/privacy-hygiene/SKILL.md +114 -0
- package/skills/qbug/SKILL.md +8 -8
- package/skills/rebase/SKILL.md +5 -1
- package/skills/refine/SKILL.md +4 -5
- package/skills/reviewer-gates/SKILL.md +7 -11
- package/skills/session-log/SKILL.md +4 -1
- package/skills/skill-builder/SKILL.md +3 -6
- package/skills/structure-prompt/SKILL.md +4 -5
- package/skills/team-advisor/SKILL.md +56 -0
- package/skills/test_markdown_link_integrity.py +10 -6
- package/skills/update/SKILL.md +5 -1
- package/skills/usage-pause/SKILL.md +14 -5
- package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
- package/skills/verified-build/SKILL.md +4 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NAS SSH Invocation Policy
|
|
2
2
|
|
|
3
|
-
**When this applies:** Any `ssh`, `scp`, or `sftp` command against the NAS
|
|
3
|
+
**When this applies:** Any `ssh`, `scp`, or `sftp` command against the NAS.
|
|
4
4
|
|
|
5
5
|
## Rule
|
|
6
6
|
|
|
@@ -9,13 +9,15 @@ Reach the NAS through the Windows OpenSSH binary with batch mode on. Git Bash's
|
|
|
9
9
|
Use this form for every NAS ssh command:
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
"/c/Windows/System32/OpenSSH/ssh.exe" -o BatchMode=yes -o ConnectTimeout=10 -p
|
|
12
|
+
"/c/Windows/System32/OpenSSH/ssh.exe" -o BatchMode=yes -o ConnectTimeout=10 -p 22 operator@nas.example.local "<cmd>"
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`scp` and `sftp` take the matching `System32/OpenSSH` binary, `-o BatchMode=yes`, and port
|
|
15
|
+
`scp` and `sftp` take the matching `System32/OpenSSH` binary, `-o BatchMode=yes`, and the same port (`-P` for `scp`).
|
|
16
|
+
|
|
17
|
+
The host, ssh user, and port come from the `CLAUDE_NAS_*` environment variables or `~/.claude/local-identity.json`; the committed examples show placeholders (`nas.example.local`, `operator`, `22`).
|
|
16
18
|
|
|
17
19
|
`-o BatchMode=yes` is required, not optional: it turns a key-authentication failure into a loud non-zero exit rather than a silent password prompt, so an auth regression surfaces as an error you can read.
|
|
18
20
|
|
|
19
21
|
## Enforcement
|
|
20
22
|
|
|
21
|
-
`nas_ssh_binary_enforcer.py` (PreToolUse on Bash) denies a bare `ssh`/`scp`/`sftp` command word aimed at
|
|
23
|
+
`nas_ssh_binary_enforcer.py` (PreToolUse on Bash) denies a bare `ssh`/`scp`/`sftp` command word aimed at the NAS host and points at the full-binary form. It also denies the full `System32/OpenSSH` binary to that host when the command omits `-o BatchMode=yes`. Commands to any other host, and commands that mention the address without an ssh-family command word, pass.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.md"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# No Justification Noise in Documentation
|
|
7
|
+
|
|
8
|
+
**When this applies:** Any Write or Edit to a `.md` file.
|
|
9
|
+
|
|
10
|
+
## Rule
|
|
11
|
+
|
|
12
|
+
Markdown states what the system is and does, in facts a reader can act on. Cut any sentence whose only job is to say why a choice is good, or to restate a gain the reader already works out from the stated behavior or from a rule a hook or another file enforces. A sentence like that carries no new fact — it repeats one the reader already holds, so it earns no space.
|
|
13
|
+
|
|
14
|
+
## The test
|
|
15
|
+
|
|
16
|
+
For each sentence, ask: **does it state a fact the reader can act on that they could not already work out from the behavior around it or from a rule enforced elsewhere?** If no, cut the sentence.
|
|
17
|
+
|
|
18
|
+
## Two shapes to catch
|
|
19
|
+
|
|
20
|
+
### Pure noise — cut the whole sentence
|
|
21
|
+
|
|
22
|
+
A sentence whose only job is to point out a result that follows from a fact the doc already states.
|
|
23
|
+
|
|
24
|
+
> Autoconverge's bug-audit and self-review lenses and pr-converge's CODE_REVIEW step point at this file and read it when they run; they do not carry its text in their spawn prompts, so the checklist stays out of the per-round token budget.
|
|
25
|
+
|
|
26
|
+
The doc already states the lenses read the file. The "so it stays out of the token budget" tail is a result the reader works out alone, so it carries nothing. Cut the sentence.
|
|
27
|
+
|
|
28
|
+
### Load-bearing first, noise after — keep the fact, cut the rest
|
|
29
|
+
|
|
30
|
+
A sentence that states a real fact, trailed by prose that only re-argues it or restates its payoff.
|
|
31
|
+
|
|
32
|
+
> The pre-catch stage drives the code to clean against these five lanes before any external reviewer sees it. External reviewers (Cursor Bugbot, GitHub Copilot) are terminal confirmation gates that run only after every lane below is clean, and they are expected to return zero findings.
|
|
33
|
+
|
|
34
|
+
Keep the first sentence — it states what the stage does. Cut the second: "run only after every lane below is clean" repeats the first sentence's claim, and "expected to return zero findings" is a hoped-for result, not a fact the reader acts on.
|
|
35
|
+
|
|
36
|
+
## What stays
|
|
37
|
+
|
|
38
|
+
- A fact the reader acts on: an input a piece of code takes, an order a producer emits, a path a script writes.
|
|
39
|
+
- A rule's one-line reason stated in terms of present behavior — `--jq` runs per page, so cross-page sorts give wrong results — because that reason names a fact the reader needs to pick the right call.
|
|
40
|
+
- A tradeoff or a constraint the reader weighs before choosing a path.
|
|
41
|
+
|
|
42
|
+
## Sibling rules
|
|
43
|
+
|
|
44
|
+
This rule sits beside three others; each cuts a different kind of dead prose.
|
|
45
|
+
|
|
46
|
+
| Rule | Cuts |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `no-historical-clutter.md` | references to old state (`previously`, `migrated from`) |
|
|
49
|
+
| `self-contained-docs.md` | references to the chat that produced the doc |
|
|
50
|
+
| `plain-language.md` | heavy words with an everyday swap |
|
|
51
|
+
| `no-justification-noise.md` | a present-tense sentence that only justifies or restates a fact the reader already holds |
|
|
52
|
+
|
|
53
|
+
`no-historical-clutter.md` keeps a rule's reason when the reason names present behavior; this rule keeps the same reason for the same test. The two agree: a reason that names a fact the reader acts on stays, and a sentence that only re-argues a stated fact goes.
|
|
54
|
+
|
|
55
|
+
## Enforcement
|
|
56
|
+
|
|
57
|
+
The AI review lane carries this rule: `AGENTS.md` names it as a finding an agent applies to the `.md` lines a PR changes. No hook backs it, because telling a justification sentence from a load-bearing one needs meaning a regex cannot read.
|
|
58
|
+
|
|
59
|
+
## Why
|
|
60
|
+
|
|
61
|
+
A sentence that repeats a fact the reader already holds costs reading time and pays back nothing.
|
|
@@ -89,7 +89,7 @@ class TestExclusionFilter:
|
|
|
89
89
|
assert filtered == []
|
|
90
90
|
|
|
91
91
|
def test_drops_path_with_dot_cache_segment(self) -> None:
|
|
92
|
-
all_candidates = ["C:\\Users\\
|
|
92
|
+
all_candidates = ["C:\\Users\\example\\.cache\\build"]
|
|
93
93
|
filtered = setup.apply_exclusion_filter(all_candidates)
|
|
94
94
|
assert filtered == []
|
|
95
95
|
|
package/skills/CLAUDE.md
CHANGED
|
@@ -17,7 +17,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
17
17
|
|
|
18
18
|
## Shared support code
|
|
19
19
|
|
|
20
|
-
`_shared/` — support code used by more than one skill. It holds `pr-loop/`, which provides prompt templates and Python helper scripts shared
|
|
20
|
+
`_shared/` — support code used by more than one skill. It holds `pr-loop/`, which provides prompt templates and Python helper scripts shared across the PR-loop skills, and `advisor/`, which provides the shared warm-advisor protocol used by `team-advisor` and `orchestrator`.
|
|
21
21
|
|
|
22
22
|
## Skill groups
|
|
23
23
|
|
|
@@ -26,8 +26,9 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
26
26
|
- `implement` — structured implementation from an existing plan packet
|
|
27
27
|
- `bdd-protocol` — BDD depth: Example Mapping, scenario quality, outside-in layout
|
|
28
28
|
- `verified-build` — build + test loop that gates on a verifier verdict
|
|
29
|
-
- `orchestrator` — turns the session into the advisor-orchestrator: it spawns executor subagents to do the code edits and test runs
|
|
30
|
-
- `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the
|
|
29
|
+
- `orchestrator` — turns the session into the advisor-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; Grok self-as-advisor on the orchestrating session)
|
|
30
|
+
- `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; Grok self-as-advisor, no Agent spawn)
|
|
31
|
+
- `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; Grok self-as-advisor) and consults it for a second opinion before a big decision, at completion, when stuck, or when reconsidering the approach
|
|
31
32
|
|
|
32
33
|
**PR review and convergence**
|
|
33
34
|
- `autoconverge` — autonomous single-run workflow that drives a PR to ready
|
|
@@ -42,6 +43,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
42
43
|
- `pr-fix-protocol` — fix, reply, and resolve reviewer findings; the unresolved-thread sweep
|
|
43
44
|
- `post-audit-findings` — publishes an audit pass as one GitHub PR review
|
|
44
45
|
- `pr-loop-lifecycle` — opens and closes a PR-loop run (grant, teardown, PR description, revoke, report)
|
|
46
|
+
- `pr-loop-cloud-transport` — six-step transport workflow that lets any PR-loop skill run in a session whose `gh` CLI is absent or cannot act on the PR (MCP schema load, origin/HEAD fix, identity rules, the gh-to-MCP substitution matrix, the Copilot status rule, and the post self-check)
|
|
45
47
|
- `code` — strict-mode code generation session
|
|
46
48
|
|
|
47
49
|
**Research and discovery**
|
|
@@ -57,6 +59,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
57
59
|
- `session-tidy` — tidies the session folder
|
|
58
60
|
- `bg-agent` — launches a background agent
|
|
59
61
|
- `task-build` — gathers open tasks
|
|
62
|
+
- `privacy-hygiene` — full-repo personal-data and secret sweep plus remediation guide
|
|
60
63
|
- `update` — updates the dev-env package
|
|
61
64
|
- `gh-paginate` — safe `gh api` pagination patterns
|
|
62
65
|
- `fresh-branch` — creates a clean branch off main
|
|
@@ -98,7 +98,7 @@ def slugify_pr_identity(owner: str, repo: str, pr_number: int) -> str:
|
|
|
98
98
|
pr_number: Pull request number.
|
|
99
99
|
|
|
100
100
|
Returns:
|
|
101
|
-
Slugified token (e.g. 'jl-cmd-claude-
|
|
101
|
+
Slugified token (e.g. 'jl-cmd-claude-dev-env-pr-422').
|
|
102
102
|
"""
|
|
103
103
|
return MULTI_PR_SLUG_TEMPLATE.format(owner=owner, repo=repo, number=pr_number)
|
|
104
104
|
|
|
@@ -179,7 +179,7 @@ def diff_patch_path(
|
|
|
179
179
|
loop_number: Current loop iteration.
|
|
180
180
|
|
|
181
181
|
Returns:
|
|
182
|
-
Absolute path (e.g. '<run_temp>/jl-cmd-claude-
|
|
182
|
+
Absolute path (e.g. '<run_temp>/jl-cmd-claude-dev-env-pr-422/loop-3.patch').
|
|
183
183
|
"""
|
|
184
184
|
slug = slugify_pr_identity(owner, repo, pr_number)
|
|
185
185
|
filename = DIFF_PATCH_TEMPLATE.format(loop=loop_number)
|
|
@@ -7,7 +7,7 @@ inline. <pr_description> carries the PR body text read from --pr-body-file,
|
|
|
7
7
|
and stays empty when that argument is absent or the file cannot be read.
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
|
-
python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-
|
|
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
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -4,7 +4,7 @@ Populates <bug> entries from findings JSON, plus <execution> checklist
|
|
|
4
4
|
and <constraints>.
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-
|
|
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
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -32,7 +32,7 @@ path_resolver = _load_path_resolver()
|
|
|
32
32
|
def test_per_pr_workspace_returns_typed_structure_with_concrete_fields() -> None:
|
|
33
33
|
run_temp_dir = Path("/tmp/bugteam-pr-422")
|
|
34
34
|
workspace = path_resolver.per_pr_workspace(
|
|
35
|
-
run_temp_dir, "jl-cmd", "claude-
|
|
35
|
+
run_temp_dir, "jl-cmd", "claude-dev-env", 422
|
|
36
36
|
)
|
|
37
37
|
assert isinstance(workspace, path_resolver.PerPrWorkspace)
|
|
38
38
|
assert isinstance(workspace.worktree, Path)
|
|
@@ -51,11 +51,11 @@ def test_per_pr_workspace_diff_patch_template_carries_loop_placeholder() -> None
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def test_per_pr_workspace_diff_patch_template_uses_forward_slashes() -> None:
|
|
54
|
-
run_temp_dir = Path("C:/Users/
|
|
54
|
+
run_temp_dir = Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376")
|
|
55
55
|
workspace = path_resolver.per_pr_workspace(run_temp_dir, "owner", "repo", 376)
|
|
56
56
|
assert "\\" not in workspace.diff_patch_template
|
|
57
57
|
assert workspace.diff_patch_template == (
|
|
58
|
-
"C:/Users/
|
|
58
|
+
"C:/Users/example/AppData/Local/Temp/bugteam-pr-376/"
|
|
59
59
|
"pr-376/owner-repo-pr-376/loop-{loop}.patch"
|
|
60
60
|
)
|
|
61
61
|
|
|
@@ -65,3 +65,42 @@ def test_per_pr_workspace_is_frozen() -> None:
|
|
|
65
65
|
workspace = path_resolver.per_pr_workspace(run_temp_dir, "owner", "repo", 9)
|
|
66
66
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
67
67
|
workspace.worktree = Path("/tmp/other")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_sanitize_branch_name_replaces_filesystem_unsafe_characters() -> None:
|
|
71
|
+
assert path_resolver.sanitize_branch_name("feat/my-branch") == "feat-my-branch"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_build_run_name_uses_pr_number_for_single_pr() -> None:
|
|
75
|
+
assert path_resolver.build_run_name(422, "feat/x", is_multi_pr=False) == "bugteam-pr-422"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_build_run_name_uses_sanitized_branch_for_multi_pr() -> None:
|
|
79
|
+
assert path_resolver.build_run_name(422, "feat/x", is_multi_pr=True) == "bugteam-feat-x"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_slugify_pr_identity_joins_owner_repo_and_number() -> None:
|
|
83
|
+
assert (
|
|
84
|
+
path_resolver.slugify_pr_identity("jl-cmd", "claude-dev-env", 422)
|
|
85
|
+
== "jl-cmd-claude-dev-env-pr-422"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_outcome_xml_path_embeds_pr_and_loop() -> None:
|
|
90
|
+
worktree_path = Path("/tmp/worktree")
|
|
91
|
+
outcome_path = path_resolver.outcome_xml_path(worktree_path, 422, 3)
|
|
92
|
+
assert outcome_path == worktree_path / ".bugteam-pr422-loop3.outcomes.xml"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_fix_outcome_xml_path_embeds_pr_and_loop() -> None:
|
|
96
|
+
worktree_path = Path("/tmp/worktree")
|
|
97
|
+
fix_outcome_path = path_resolver.fix_outcome_xml_path(worktree_path, 422, 3)
|
|
98
|
+
assert fix_outcome_path == worktree_path / ".bugteam-pr422-loop3.fix-outcomes.xml"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_diff_patch_path_joins_slug_and_loop_filename() -> None:
|
|
102
|
+
run_temp_dir = Path("/tmp/run")
|
|
103
|
+
patch_path = path_resolver.diff_patch_path(
|
|
104
|
+
run_temp_dir, "owner", "repo", 7, 2
|
|
105
|
+
)
|
|
106
|
+
assert patch_path == run_temp_dir / "owner-repo-pr-7" / "loop-2.patch"
|
|
@@ -50,7 +50,7 @@ def _rubric_label_by_letter() -> dict[str, str]:
|
|
|
50
50
|
def _build_audit_root() -> Element:
|
|
51
51
|
return build_audit_prompt.build_audit_prompt_xml(
|
|
52
52
|
owner="jl-cmd",
|
|
53
|
-
repo="claude-
|
|
53
|
+
repo="claude-dev-env",
|
|
54
54
|
pr_number=422,
|
|
55
55
|
loop=1,
|
|
56
56
|
head_ref="feat/branch",
|
|
@@ -63,25 +63,25 @@ def _build_audit_root() -> Element:
|
|
|
63
63
|
def test_context_and_scope_render_paths_with_forward_slashes() -> None:
|
|
64
64
|
root = build_audit_prompt.build_audit_prompt_xml(
|
|
65
65
|
owner="jl-cmd",
|
|
66
|
-
repo="claude-
|
|
66
|
+
repo="claude-dev-env",
|
|
67
67
|
pr_number=376,
|
|
68
68
|
loop=1,
|
|
69
69
|
head_ref="feat/branch",
|
|
70
70
|
base_ref="main",
|
|
71
|
-
worktree_path=Path("C:/Users/
|
|
72
|
-
run_temp_dir=Path("C:/Users/
|
|
71
|
+
worktree_path=Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree"),
|
|
72
|
+
run_temp_dir=Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376"),
|
|
73
73
|
)
|
|
74
74
|
context = root.find("context")
|
|
75
75
|
assert context is not None
|
|
76
76
|
worktree_text = context.findtext("worktree_path")
|
|
77
77
|
run_temp_text = context.findtext("run_temp_dir")
|
|
78
|
-
assert worktree_text == "C:/Users/
|
|
79
|
-
assert run_temp_text == "C:/Users/
|
|
78
|
+
assert worktree_text == "C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree"
|
|
79
|
+
assert run_temp_text == "C:/Users/example/AppData/Local/Temp/bugteam-pr-376"
|
|
80
80
|
scope = root.find("scope")
|
|
81
81
|
assert scope is not None
|
|
82
82
|
assert scope.text is not None
|
|
83
83
|
assert "\\" not in scope.text
|
|
84
|
-
assert "C:/Users/
|
|
84
|
+
assert "C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree" in scope.text
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def test_bug_categories_carry_ids_a_through_q_in_order() -> None:
|
|
@@ -179,7 +179,7 @@ def test_bug_categories_include_category_q() -> None:
|
|
|
179
179
|
def test_pr_description_carries_body_text_when_supplied() -> None:
|
|
180
180
|
root = build_audit_prompt.build_audit_prompt_xml(
|
|
181
181
|
owner="jl-cmd",
|
|
182
|
-
repo="claude-
|
|
182
|
+
repo="claude-dev-env",
|
|
183
183
|
pr_number=422,
|
|
184
184
|
loop=1,
|
|
185
185
|
head_ref="feat/branch",
|
|
@@ -218,7 +218,7 @@ def test_read_pr_body_text_returns_none_when_file_missing(tmp_path: Path) -> Non
|
|
|
218
218
|
def test_emit_audit_prompt_embeds_pr_body_text_in_pr_description() -> None:
|
|
219
219
|
xml_text = build_audit_prompt.emit_audit_prompt(
|
|
220
220
|
owner="jl-cmd",
|
|
221
|
-
repo="claude-
|
|
221
|
+
repo="claude-dev-env",
|
|
222
222
|
pr_number=422,
|
|
223
223
|
loop=1,
|
|
224
224
|
head_ref="feat/branch",
|
|
@@ -233,7 +233,7 @@ def test_emit_audit_prompt_embeds_pr_body_text_in_pr_description() -> None:
|
|
|
233
233
|
def test_parse_arguments_defaults_pr_body_file_to_none() -> None:
|
|
234
234
|
arguments = build_audit_prompt.parse_arguments([
|
|
235
235
|
"--owner", "jl-cmd",
|
|
236
|
-
"--repo", "claude-
|
|
236
|
+
"--repo", "claude-dev-env",
|
|
237
237
|
"--pr-number", "422",
|
|
238
238
|
"--loop", "1",
|
|
239
239
|
"--head-ref", "feat/branch",
|
|
@@ -247,7 +247,7 @@ def test_parse_arguments_defaults_pr_body_file_to_none() -> None:
|
|
|
247
247
|
def test_parse_arguments_reads_pr_body_file_path() -> None:
|
|
248
248
|
arguments = build_audit_prompt.parse_arguments([
|
|
249
249
|
"--owner", "jl-cmd",
|
|
250
|
-
"--repo", "claude-
|
|
250
|
+
"--repo", "claude-dev-env",
|
|
251
251
|
"--pr-number", "422",
|
|
252
252
|
"--loop", "1",
|
|
253
253
|
"--head-ref", "feat/branch",
|
|
@@ -35,15 +35,38 @@ def test_context_worktree_path_renders_with_forward_slashes(tmp_path: Path) -> N
|
|
|
35
35
|
)
|
|
36
36
|
root = build_fix_prompt.build_fix_prompt_xml(
|
|
37
37
|
owner="jl-cmd",
|
|
38
|
-
repo="claude-
|
|
38
|
+
repo="claude-dev-env",
|
|
39
39
|
pr_number=376,
|
|
40
40
|
loop=1,
|
|
41
41
|
head_ref="feat/branch",
|
|
42
42
|
base_ref="main",
|
|
43
|
-
worktree_path=Path("C:/Users/
|
|
43
|
+
worktree_path=Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree"),
|
|
44
44
|
findings_json_path=findings_json_path,
|
|
45
45
|
)
|
|
46
46
|
context = root.find("context")
|
|
47
47
|
assert context is not None
|
|
48
48
|
worktree_text = context.findtext("worktree_path")
|
|
49
|
-
assert worktree_text == "C:/Users/
|
|
49
|
+
assert worktree_text == "C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_emit_fix_prompt_returns_xml_string_with_worktree_path(tmp_path: Path) -> None:
|
|
53
|
+
findings_json_path = tmp_path / "findings.json"
|
|
54
|
+
findings_json_path.write_text(
|
|
55
|
+
json.dumps([{"severity": "P1", "file": "a.py", "line": 1}]),
|
|
56
|
+
encoding="utf-8",
|
|
57
|
+
)
|
|
58
|
+
worktree_path = Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree")
|
|
59
|
+
xml_text = build_fix_prompt.emit_fix_prompt(
|
|
60
|
+
owner="jl-cmd",
|
|
61
|
+
repo="claude-dev-env",
|
|
62
|
+
pr_number=376,
|
|
63
|
+
loop=1,
|
|
64
|
+
head_ref="feat/branch",
|
|
65
|
+
base_ref="main",
|
|
66
|
+
worktree_path=worktree_path,
|
|
67
|
+
findings_json_path=findings_json_path,
|
|
68
|
+
)
|
|
69
|
+
assert isinstance(xml_text, str)
|
|
70
|
+
assert "C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree" in xml_text
|
|
71
|
+
assert 'role="fix"' in xml_text
|
|
72
|
+
assert "<spawn_prompt" in xml_text
|
|
@@ -29,7 +29,7 @@ def _load_preflight() -> ModuleType:
|
|
|
29
29
|
preflight = _load_preflight()
|
|
30
30
|
|
|
31
31
|
GIT_TIMEOUT = 30
|
|
32
|
-
CLAUDE_REMOTE = "https://github.com/jl-cmd/claude-
|
|
32
|
+
CLAUDE_REMOTE = "https://github.com/jl-cmd/claude-dev-env.git"
|
|
33
33
|
GIT_CEILING_DIRECTORIES = "GIT_CEILING_DIRECTORIES"
|
|
34
34
|
|
|
35
35
|
|
|
@@ -68,20 +68,20 @@ def _init_repo(repo_dir: Path, origin_url: str | None) -> Path:
|
|
|
68
68
|
("remote_url", "expected_owner", "expected_repo"),
|
|
69
69
|
[
|
|
70
70
|
(
|
|
71
|
-
"https://github.com/jl-cmd/claude-
|
|
71
|
+
"https://github.com/jl-cmd/claude-dev-env.git",
|
|
72
72
|
"jl-cmd",
|
|
73
|
-
"claude-
|
|
73
|
+
"claude-dev-env",
|
|
74
74
|
),
|
|
75
75
|
(
|
|
76
|
-
"https://github.com/jl-cmd/claude-
|
|
76
|
+
"https://github.com/jl-cmd/claude-dev-env",
|
|
77
77
|
"jl-cmd",
|
|
78
|
-
"claude-
|
|
78
|
+
"claude-dev-env",
|
|
79
79
|
),
|
|
80
|
-
("git@github.com:
|
|
80
|
+
("git@github.com:Example/llm-settings.git", "example", "llm-settings"),
|
|
81
81
|
(
|
|
82
|
-
"ssh://git@github.com/jl-cmd/Claude-
|
|
82
|
+
"ssh://git@github.com/jl-cmd/Claude-Dev-Env",
|
|
83
83
|
"jl-cmd",
|
|
84
|
-
"claude-
|
|
84
|
+
"claude-dev-env",
|
|
85
85
|
),
|
|
86
86
|
("https://github.com/jl-cmd/repo/", "jl-cmd", "repo"),
|
|
87
87
|
],
|
|
@@ -103,9 +103,33 @@ def test_parse_repo_identity_rejects_non_github_remotes(remote_url: str) -> None
|
|
|
103
103
|
assert preflight.parse_repo_identity(remote_url) is None
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
def test_is_inside_work_tree_true_in_git_repo(tmp_path: Path) -> None:
|
|
107
|
+
repo_dir = _init_repo(tmp_path / "session", None)
|
|
108
|
+
assert preflight.is_inside_work_tree(repo_dir) is True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_is_inside_work_tree_false_in_plain_directory(tmp_path: Path) -> None:
|
|
112
|
+
plain_dir = tmp_path / "home"
|
|
113
|
+
plain_dir.mkdir()
|
|
114
|
+
assert preflight.is_inside_work_tree(plain_dir) is False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_worktree_machinery_is_healthy_true_in_git_repo(tmp_path: Path) -> None:
|
|
118
|
+
repo_dir = _init_repo(tmp_path / "session", None)
|
|
119
|
+
assert preflight.worktree_machinery_is_healthy(repo_dir) is True
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_worktree_machinery_is_healthy_false_in_plain_directory(
|
|
123
|
+
tmp_path: Path,
|
|
124
|
+
) -> None:
|
|
125
|
+
plain_dir = tmp_path / "home"
|
|
126
|
+
plain_dir.mkdir()
|
|
127
|
+
assert preflight.worktree_machinery_is_healthy(plain_dir) is False
|
|
128
|
+
|
|
129
|
+
|
|
106
130
|
def test_classify_same_repo_when_origin_matches_pr(tmp_path: Path) -> None:
|
|
107
131
|
repo_dir = _init_repo(tmp_path / "session", CLAUDE_REMOTE)
|
|
108
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
132
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
109
133
|
verdict = preflight.classify_environment(repo_dir, pr_identity)
|
|
110
134
|
assert verdict.outcome == preflight.OUTCOME_SAME_REPO
|
|
111
135
|
assert verdict.cwd_identity == pr_identity
|
|
@@ -116,17 +140,17 @@ def test_classify_different_repo_when_origin_is_another_github_repo(
|
|
|
116
140
|
tmp_path: Path,
|
|
117
141
|
) -> None:
|
|
118
142
|
repo_dir = _init_repo(
|
|
119
|
-
tmp_path / "session", "https://github.com/
|
|
143
|
+
tmp_path / "session", "https://github.com/example/llm-settings.git"
|
|
120
144
|
)
|
|
121
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
145
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
122
146
|
verdict = preflight.classify_environment(repo_dir, pr_identity)
|
|
123
147
|
assert verdict.outcome == preflight.OUTCOME_DIFFERENT_REPO
|
|
124
|
-
assert verdict.cwd_identity == preflight.RepoIdentity("
|
|
148
|
+
assert verdict.cwd_identity == preflight.RepoIdentity("example", "llm-settings")
|
|
125
149
|
|
|
126
150
|
|
|
127
151
|
def test_classify_different_repo_when_origin_is_non_github(tmp_path: Path) -> None:
|
|
128
152
|
repo_dir = _init_repo(tmp_path / "session", "https://gitlab.com/foo/bar.git")
|
|
129
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
153
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
130
154
|
verdict = preflight.classify_environment(repo_dir, pr_identity)
|
|
131
155
|
assert verdict.outcome == preflight.OUTCOME_DIFFERENT_REPO
|
|
132
156
|
assert verdict.cwd_identity is None
|
|
@@ -134,7 +158,7 @@ def test_classify_different_repo_when_origin_is_non_github(tmp_path: Path) -> No
|
|
|
134
158
|
|
|
135
159
|
def test_classify_re_rooted_when_origin_is_absent(tmp_path: Path) -> None:
|
|
136
160
|
repo_dir = _init_repo(tmp_path / "session", None)
|
|
137
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
161
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
138
162
|
verdict = preflight.classify_environment(repo_dir, pr_identity)
|
|
139
163
|
assert verdict.outcome == preflight.OUTCOME_RE_ROOTED
|
|
140
164
|
|
|
@@ -144,7 +168,7 @@ def test_classify_re_rooted_when_directory_is_not_a_work_tree(
|
|
|
144
168
|
) -> None:
|
|
145
169
|
plain_dir = tmp_path / "home"
|
|
146
170
|
plain_dir.mkdir()
|
|
147
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
171
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
148
172
|
verdict = preflight.classify_environment(plain_dir, pr_identity)
|
|
149
173
|
assert verdict.outcome == preflight.OUTCOME_RE_ROOTED
|
|
150
174
|
assert verdict.has_healthy_worktree_machinery is False
|
|
@@ -177,10 +201,10 @@ def test_decide_exit_code_matrix(
|
|
|
177
201
|
def test_build_report_lines_marks_outcome_and_routes_cross_repo() -> None:
|
|
178
202
|
verdict = preflight.PreflightVerdict(
|
|
179
203
|
outcome=preflight.OUTCOME_DIFFERENT_REPO,
|
|
180
|
-
cwd_identity=preflight.RepoIdentity("
|
|
204
|
+
cwd_identity=preflight.RepoIdentity("example", "llm-settings"),
|
|
181
205
|
has_healthy_worktree_machinery=True,
|
|
182
206
|
)
|
|
183
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
207
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
184
208
|
classify_lines = preflight.build_report_lines(
|
|
185
209
|
verdict, "classify", Path("/tmp/x"), pr_identity
|
|
186
210
|
)
|
|
@@ -195,10 +219,10 @@ def test_build_report_lines_marks_outcome_and_routes_cross_repo() -> None:
|
|
|
195
219
|
def test_build_report_lines_aborts_on_broken_worktree_machinery() -> None:
|
|
196
220
|
verdict = preflight.PreflightVerdict(
|
|
197
221
|
outcome=preflight.OUTCOME_SAME_REPO,
|
|
198
|
-
cwd_identity=preflight.RepoIdentity("jl-cmd", "claude-
|
|
222
|
+
cwd_identity=preflight.RepoIdentity("jl-cmd", "claude-dev-env"),
|
|
199
223
|
has_healthy_worktree_machinery=False,
|
|
200
224
|
)
|
|
201
|
-
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-
|
|
225
|
+
pr_identity = preflight.RepoIdentity("jl-cmd", "claude-dev-env")
|
|
202
226
|
lines = preflight.build_report_lines(verdict, "strict", Path("/tmp/x"), pr_identity)
|
|
203
227
|
assert lines[0] == "PREFLIGHT_OUTCOME=same_repo"
|
|
204
228
|
assert any("git worktree prune" in line for line in lines)
|
|
@@ -210,7 +234,7 @@ def test_main_strict_passes_in_matching_repo(
|
|
|
210
234
|
repo_dir = _init_repo(tmp_path / "session", CLAUDE_REMOTE)
|
|
211
235
|
monkeypatch.chdir(repo_dir)
|
|
212
236
|
exit_code = preflight.main(
|
|
213
|
-
["--owner", "jl-cmd", "--repo", "claude-
|
|
237
|
+
["--owner", "jl-cmd", "--repo", "claude-dev-env", "--mode", "strict"]
|
|
214
238
|
)
|
|
215
239
|
captured = capsys.readouterr()
|
|
216
240
|
assert exit_code == preflight.EXIT_PREFLIGHT_OK
|
|
@@ -221,11 +245,11 @@ def test_main_strict_aborts_when_session_rooted_in_other_repo(
|
|
|
221
245
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
222
246
|
) -> None:
|
|
223
247
|
repo_dir = _init_repo(
|
|
224
|
-
tmp_path / "session", "https://github.com/
|
|
248
|
+
tmp_path / "session", "https://github.com/example/llm-settings.git"
|
|
225
249
|
)
|
|
226
250
|
monkeypatch.chdir(repo_dir)
|
|
227
251
|
exit_code = preflight.main(
|
|
228
|
-
["--owner", "jl-cmd", "--repo", "claude-
|
|
252
|
+
["--owner", "jl-cmd", "--repo", "claude-dev-env", "--mode", "strict"]
|
|
229
253
|
)
|
|
230
254
|
captured = capsys.readouterr()
|
|
231
255
|
assert exit_code == preflight.EXIT_PREFLIGHT_ABORT
|
|
@@ -237,11 +261,11 @@ def test_main_classify_routes_cross_repo_without_abort(
|
|
|
237
261
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
238
262
|
) -> None:
|
|
239
263
|
repo_dir = _init_repo(
|
|
240
|
-
tmp_path / "session", "https://github.com/
|
|
264
|
+
tmp_path / "session", "https://github.com/example/llm-settings.git"
|
|
241
265
|
)
|
|
242
266
|
monkeypatch.chdir(repo_dir)
|
|
243
267
|
exit_code = preflight.main(
|
|
244
|
-
["--owner", "jl-cmd", "--repo", "claude-
|
|
268
|
+
["--owner", "jl-cmd", "--repo", "claude-dev-env", "--mode", "classify"]
|
|
245
269
|
)
|
|
246
270
|
captured = capsys.readouterr()
|
|
247
271
|
assert exit_code == preflight.EXIT_PREFLIGHT_OK
|
|
@@ -256,7 +280,7 @@ def test_main_classify_aborts_when_re_rooted(
|
|
|
256
280
|
plain_dir.mkdir()
|
|
257
281
|
monkeypatch.chdir(plain_dir)
|
|
258
282
|
exit_code = preflight.main(
|
|
259
|
-
["--owner", "jl-cmd", "--repo", "claude-
|
|
283
|
+
["--owner", "jl-cmd", "--repo", "claude-dev-env", "--mode", "classify"]
|
|
260
284
|
)
|
|
261
285
|
captured = capsys.readouterr()
|
|
262
286
|
assert exit_code == preflight.EXIT_PREFLIGHT_ABORT
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Trigger:** `/autoconverge`, "autoconverge this PR", "converge this PR in one run", "run the converge workflow", "drive the PR to ready autonomously".
|
|
4
4
|
|
|
5
|
-
Drives one draft PR to convergence in a single autonomous workflow run. Each round runs
|
|
5
|
+
Drives one draft PR to convergence in a single autonomous workflow run. Each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies. Once the internal lenses are clean, Cursor Bugbot and Copilot run as terminal confirmation gates, and the run marks the PR ready on convergence. State lives in the workflow journal; no `ScheduleWakeup` ticks.
|
|
6
6
|
|
|
7
7
|
## Subdirectories
|
|
8
8
|
|
|
@@ -16,11 +16,11 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
|
|
|
16
16
|
| File | Role |
|
|
17
17
|
|---|---|
|
|
18
18
|
| `SKILL.md` | Full entry-point protocol: pre-flight steps, worktree setup, `Workflow` call, budget-aware round boundaries, teardown, and the per-round convergence loop summary. |
|
|
19
|
-
| `workflow/converge.mjs` | Main convergence workflow. Runs
|
|
19
|
+
| `workflow/converge.mjs` | Main convergence workflow. Runs a static sweep then the internal lenses each round, applies fixes, runs Bugbot and Copilot as terminal gates, checks convergence, and marks the PR ready. |
|
|
20
20
|
| `workflow/aggregate_runs.py` | Merges every autoconverge journal for a PR into one deduped journal. |
|
|
21
21
|
| `workflow/convergence_summary.py` | Builds the convergence-summary agent prompt over the merged findings. |
|
|
22
22
|
| `workflow/render_report.py` | Builds the closing HTML report from the merged journal and summary. |
|
|
23
|
-
| `reference/convergence.md` | Round shape: the three parallel lenses, deduplication, fix commit, and the ready definition. |
|
|
23
|
+
| `reference/convergence.md` | Round shape: the static sweep, the three parallel internal lenses, deduplication, fix commit, the terminal Bugbot and Copilot gates, and the ready definition. |
|
|
24
24
|
| `reference/stop-conditions.md` | Every way the run ends short of ready. |
|
|
25
25
|
| `reference/gotchas.md` | Hard-won failure lessons. |
|
|
26
26
|
| `reference/closing-report.md` | Specification for the closing HTML report format. |
|