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
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: copilot-finding-triage
|
|
3
|
+
description: >-
|
|
4
|
+
Sorts each Copilot gate finding from a converge run into one of two tiers and
|
|
5
|
+
routes it: a self-healing finding is auto-fixed, pushed, and counted toward
|
|
6
|
+
convergence with no user page, while a code concern pages the user over ntfy
|
|
7
|
+
and holds the run behind a 45-minute gate. Triggers: 'copilot finding triage',
|
|
8
|
+
'user review gate', 'tier copilot findings', 'triage the copilot gate'.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Copilot Finding Triage
|
|
12
|
+
|
|
13
|
+
After a converge run's Copilot gate returns its findings, this skill sorts each
|
|
14
|
+
one into a tier and acts on it. Findings scoped to style, type hints, or tests
|
|
15
|
+
heal themselves inside the run. A logic, security, or behavior finding pages the user
|
|
16
|
+
and holds the run until they answer or a 45-minute deadline passes.
|
|
17
|
+
|
|
18
|
+
## When this applies
|
|
19
|
+
|
|
20
|
+
Reach for this skill when a Copilot review on the round's HEAD returns findings
|
|
21
|
+
inside one of these callers:
|
|
22
|
+
|
|
23
|
+
- `autoconverge` — the single-run converge workflow, at its Copilot wait-gate.
|
|
24
|
+
- `pr-converge` — the looping converge workflow, at each Copilot tick.
|
|
25
|
+
- `copilot-review` — the standalone Copilot babysitter.
|
|
26
|
+
|
|
27
|
+
Each caller already fetches the review and carries a fix-and-push flow. This
|
|
28
|
+
skill decides, per finding, whether that flow runs on its own or waits behind
|
|
29
|
+
the user gate.
|
|
30
|
+
|
|
31
|
+
## The tier split
|
|
32
|
+
|
|
33
|
+
Read the complete rubric in `reference/tier-rubric.md`. The short form:
|
|
34
|
+
|
|
35
|
+
- **SELF-HEALING** — the fix cannot change what a production caller sees at
|
|
36
|
+
runtime. Style, type hints, misplaced or unused imports, magic-value
|
|
37
|
+
extraction, reshapes limited to tests, doc-versus-code mismatches, and
|
|
38
|
+
de-duplication.
|
|
39
|
+
- **CODE CONCERN** — the fix changes observable production behavior or needs a
|
|
40
|
+
product decision. Logic or correctness defects, security, data handling,
|
|
41
|
+
error-handling semantics, and concurrency.
|
|
42
|
+
|
|
43
|
+
A finding that straddles both tiers sorts to CODE CONCERN. Any doubt routes to
|
|
44
|
+
the user gate.
|
|
45
|
+
|
|
46
|
+
## Self-healing flow
|
|
47
|
+
|
|
48
|
+
A self-healing finding never pages the user.
|
|
49
|
+
|
|
50
|
+
1. Fix the finding through the caller's existing fix flow.
|
|
51
|
+
2. Verify the fix.
|
|
52
|
+
3. Commit and push on the caller's branch.
|
|
53
|
+
4. Count the round toward convergence.
|
|
54
|
+
|
|
55
|
+
## User gate protocol
|
|
56
|
+
|
|
57
|
+
Run this protocol when one or more CODE CONCERN findings sit on the round's HEAD.
|
|
58
|
+
|
|
59
|
+
### Step 1 — Page the user
|
|
60
|
+
|
|
61
|
+
Run `scripts/notify_ntfy.py` with:
|
|
62
|
+
|
|
63
|
+
- `--title` naming the PR.
|
|
64
|
+
- `--message` summarizing each code-concern finding, one line each, as
|
|
65
|
+
`file:line — severity — one sentence`. Build the body from
|
|
66
|
+
`templates/notification.md`.
|
|
67
|
+
- `--click-url` set to the Copilot review URL, so tapping the page opens the
|
|
68
|
+
review.
|
|
69
|
+
|
|
70
|
+
The topic reads from the `NTFY_TOPIC` environment variable, and the server
|
|
71
|
+
reads from `NTFY_SERVER` with a default of `https://ntfy.sh`. A missing topic
|
|
72
|
+
fails loudly with a clear message. A failed page holds the gate open; it never
|
|
73
|
+
counts as approval.
|
|
74
|
+
|
|
75
|
+
### Step 2 — Hold and wait
|
|
76
|
+
|
|
77
|
+
Arm `ScheduleWakeup` for a 45-minute deadline. Where `ScheduleWakeup` is absent,
|
|
78
|
+
use `send_later`. The clock starts when the page reaches the user, which is the
|
|
79
|
+
moment the script exits zero.
|
|
80
|
+
|
|
81
|
+
- If the user answers, follow their direction.
|
|
82
|
+
- If the deadline passes with no answer, run the caller's normal teardown and
|
|
83
|
+
report the un-reviewed findings in the final report.
|
|
84
|
+
|
|
85
|
+
### Step 3 — Self-healing findings run in parallel
|
|
86
|
+
|
|
87
|
+
Self-healing findings on the same HEAD do not wait for the gate. Fix, verify,
|
|
88
|
+
commit, and push them through the caller's fix flow, and count the round toward
|
|
89
|
+
convergence.
|
|
90
|
+
|
|
91
|
+
## Gate checklist
|
|
92
|
+
|
|
93
|
+
- [ ] Every finding on HEAD carries a tier.
|
|
94
|
+
- [ ] Each self-healing finding is fixed, verified, and pushed.
|
|
95
|
+
- [ ] Each code-concern finding appears as one line in the ntfy body.
|
|
96
|
+
- [ ] The page carries the PR name, the per-finding summary, and the review URL.
|
|
97
|
+
- [ ] `scripts/notify_ntfy.py` exited zero before the 45-minute clock started.
|
|
98
|
+
- [ ] The wakeup is armed for 45 minutes from a delivered page.
|
|
99
|
+
- [ ] A failed page kept the gate open rather than approving the round.
|
|
100
|
+
|
|
101
|
+
## Files
|
|
102
|
+
|
|
103
|
+
| Path | Role |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `SKILL.md` | This hub: when it applies, the tier split, the gate protocol. |
|
|
106
|
+
| `reference/tier-rubric.md` | The complete tier rubric and the behavior-safe test. |
|
|
107
|
+
| `templates/notification.md` | The ntfy message body for a code-concern page. |
|
|
108
|
+
| `scripts/notify_ntfy.py` | The ntfy publish CLI. |
|
|
109
|
+
| `scripts/test_notify_ntfy.py` | Tests for the publish CLI. |
|
|
110
|
+
|
|
111
|
+
## Gotchas
|
|
112
|
+
|
|
113
|
+
- **A cloud MCP session refuses APPROVE reviews.** Any review this protocol posts
|
|
114
|
+
is event COMMENT. A cloud session cannot post an APPROVE event, so route
|
|
115
|
+
approvals through the user, never through a posted review.
|
|
116
|
+
- **A failed page is not consent.** A failed ntfy POST holds the gate open. It
|
|
117
|
+
does not auto-approve the round. Read the script's non-zero exit as a page that
|
|
118
|
+
never reached the user, and keep the run held.
|
|
119
|
+
- **Doubt sorts to CODE CONCERN.** When a finding could sit in either tier,
|
|
120
|
+
classify it as a code concern and page the user. The safe default never
|
|
121
|
+
auto-fixes a finding that might change runtime behavior.
|
|
122
|
+
- **The 45-minute clock starts at page success.** The timer starts when the page
|
|
123
|
+
reaches the user, which is the moment `scripts/notify_ntfy.py` exits zero, not
|
|
124
|
+
the moment the finding is classified.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Tier Rubric
|
|
2
|
+
|
|
3
|
+
Every Copilot finding on the round's HEAD sorts into one of two tiers. The tier
|
|
4
|
+
decides who acts: the run fixes a self-healing finding on its own, and the run
|
|
5
|
+
pages the user for a code concern.
|
|
6
|
+
|
|
7
|
+
## The two tiers
|
|
8
|
+
|
|
9
|
+
| Tier | Who acts | Page the user | Counts toward convergence |
|
|
10
|
+
|------|----------|---------------|---------------------------|
|
|
11
|
+
| SELF-HEALING | The run auto-fixes | No | Yes |
|
|
12
|
+
| CODE CONCERN | The user decides | Yes | Held until the user answers or the deadline passes |
|
|
13
|
+
|
|
14
|
+
## The test that splits them
|
|
15
|
+
|
|
16
|
+
A finding is SELF-HEALING when its fix cannot change what a production caller
|
|
17
|
+
observes at runtime. A finding is a CODE CONCERN when its fix changes runtime
|
|
18
|
+
behavior for a production caller or asks for a product decision.
|
|
19
|
+
|
|
20
|
+
## SELF-HEALING findings
|
|
21
|
+
|
|
22
|
+
The fix is safe because it leaves runtime behavior the same for every production
|
|
23
|
+
caller.
|
|
24
|
+
|
|
25
|
+
- Pure style: formatting, blank lines, naming.
|
|
26
|
+
- Type hints.
|
|
27
|
+
- Misplaced or unused imports.
|
|
28
|
+
- Magic-value extraction to a named constant.
|
|
29
|
+
- Test-only changes: a redundant or tautological test, a test reshape.
|
|
30
|
+
- A description or doc that reads differently from the code it describes.
|
|
31
|
+
- De-duplicating code that already runs the same way.
|
|
32
|
+
|
|
33
|
+
## CODE CONCERN findings
|
|
34
|
+
|
|
35
|
+
The fix changes what a production caller sees, or the call needs a person to
|
|
36
|
+
weigh a tradeoff.
|
|
37
|
+
|
|
38
|
+
- Logic or correctness defects.
|
|
39
|
+
- Security issues.
|
|
40
|
+
- Data-handling changes.
|
|
41
|
+
- Error-handling semantics.
|
|
42
|
+
- Concurrency.
|
|
43
|
+
- Anything whose fix changes observable production behavior.
|
|
44
|
+
|
|
45
|
+
## When a finding sits on the line
|
|
46
|
+
|
|
47
|
+
A finding that touches both tiers sorts to CODE CONCERN. Any doubt about whether
|
|
48
|
+
a fix stays behavior-safe routes the finding to the user gate. The safe default
|
|
49
|
+
pages a person; it never auto-fixes a finding that might change runtime behavior.
|
|
File without changes
|
package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""ntfy publish settings the notify_ntfy script reads.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
DEFAULT_SERVER_URL "https://ntfy.sh" server when NTFY_SERVER is unset
|
|
6
|
+
DEFAULT_PRIORITY_NAME "high" level when no priority is given
|
|
7
|
+
REQUEST_TIMEOUT_SECONDS 10 the POST deadline in seconds
|
|
8
|
+
|
|
9
|
+
Header names, the priority-level map, and the environment-variable names round
|
|
10
|
+
out the set.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
TOPIC_ENVIRONMENT_VARIABLE_NAME = "NTFY_TOPIC"
|
|
14
|
+
SERVER_ENVIRONMENT_VARIABLE_NAME = "NTFY_SERVER"
|
|
15
|
+
DEFAULT_SERVER_URL = "https://ntfy.sh"
|
|
16
|
+
REQUEST_TIMEOUT_SECONDS = 10
|
|
17
|
+
URL_PATH_SEPARATOR = "/"
|
|
18
|
+
MESSAGE_ENCODING = "utf-8"
|
|
19
|
+
POST_METHOD_NAME = "POST"
|
|
20
|
+
TITLE_HEADER_NAME = "Title"
|
|
21
|
+
PRIORITY_HEADER_NAME = "Priority"
|
|
22
|
+
CLICK_HEADER_NAME = "Click"
|
|
23
|
+
CONTENT_TYPE_HEADER_NAME = "Content-Type"
|
|
24
|
+
PLAIN_TEXT_CONTENT_TYPE = "text/plain"
|
|
25
|
+
DEFAULT_PRIORITY_NAME = "high"
|
|
26
|
+
ALL_PRIORITY_LEVELS_BY_NAME: dict[str, str] = {
|
|
27
|
+
"min": "1",
|
|
28
|
+
"low": "2",
|
|
29
|
+
"default": "3",
|
|
30
|
+
"high": "4",
|
|
31
|
+
"urgent": "5",
|
|
32
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Publish one ntfy notification for the Copilot gate.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
run: notify_ntfy.py --title "PR 743" --message "..." --click-url "https://r"
|
|
6
|
+
ok: topic set -> POST to {server}/{topic}, exit 0
|
|
7
|
+
flag: topic unset -> readable error on stderr, exit 1
|
|
8
|
+
|
|
9
|
+
The topic and the optional server override both read from the environment. A
|
|
10
|
+
code-concern gate runs this to page the user, then holds the run until the
|
|
11
|
+
deadline.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
notify_ntfy.py --title "PR 743" --message "..." --click-url "https://..."
|
|
15
|
+
notify_ntfy.py --title "PR 743" --message "..." --click-url "..." --priority urgent
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import urllib.error
|
|
24
|
+
import urllib.request
|
|
25
|
+
from collections.abc import Mapping
|
|
26
|
+
|
|
27
|
+
from copilot_finding_triage_constants.config.notify_ntfy_constants import (
|
|
28
|
+
ALL_PRIORITY_LEVELS_BY_NAME,
|
|
29
|
+
CLICK_HEADER_NAME,
|
|
30
|
+
CONTENT_TYPE_HEADER_NAME,
|
|
31
|
+
DEFAULT_PRIORITY_NAME,
|
|
32
|
+
DEFAULT_SERVER_URL,
|
|
33
|
+
MESSAGE_ENCODING,
|
|
34
|
+
PLAIN_TEXT_CONTENT_TYPE,
|
|
35
|
+
POST_METHOD_NAME,
|
|
36
|
+
PRIORITY_HEADER_NAME,
|
|
37
|
+
REQUEST_TIMEOUT_SECONDS,
|
|
38
|
+
SERVER_ENVIRONMENT_VARIABLE_NAME,
|
|
39
|
+
TITLE_HEADER_NAME,
|
|
40
|
+
TOPIC_ENVIRONMENT_VARIABLE_NAME,
|
|
41
|
+
URL_PATH_SEPARATOR,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def parse_arguments(all_arguments: list[str]) -> argparse.Namespace:
|
|
45
|
+
"""Parse the notify_ntfy command-line arguments.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
all_arguments: The argument strings following the script name.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The parsed namespace carrying title, message, click_url, and priority.
|
|
52
|
+
"""
|
|
53
|
+
parser = argparse.ArgumentParser(description="Publish one ntfy notification.")
|
|
54
|
+
parser.add_argument("--title", required=True)
|
|
55
|
+
parser.add_argument("--message", required=True)
|
|
56
|
+
parser.add_argument("--click-url", required=True, dest="click_url")
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--priority",
|
|
59
|
+
default=DEFAULT_PRIORITY_NAME,
|
|
60
|
+
choices=sorted(ALL_PRIORITY_LEVELS_BY_NAME),
|
|
61
|
+
)
|
|
62
|
+
return parser.parse_args(all_arguments)
|
|
63
|
+
|
|
64
|
+
def resolve_topic(all_environment_variables: Mapping[str, str]) -> str:
|
|
65
|
+
"""Return the ntfy topic from the environment, empty when unset.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
all_environment_variables: The environment mapping to read the topic from.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The topic string, or an empty string when the variable is unset.
|
|
72
|
+
"""
|
|
73
|
+
return all_environment_variables.get(TOPIC_ENVIRONMENT_VARIABLE_NAME, "").strip()
|
|
74
|
+
|
|
75
|
+
def resolve_server(all_environment_variables: Mapping[str, str]) -> str:
|
|
76
|
+
"""Return the ntfy server URL from the environment.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
all_environment_variables: The environment mapping to read the override from.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The override server URL when set, otherwise the default server URL.
|
|
83
|
+
"""
|
|
84
|
+
configured_server = all_environment_variables.get(
|
|
85
|
+
SERVER_ENVIRONMENT_VARIABLE_NAME, ""
|
|
86
|
+
).strip()
|
|
87
|
+
return configured_server or DEFAULT_SERVER_URL
|
|
88
|
+
|
|
89
|
+
def _build_headers(title: str, click_url: str, priority_name: str) -> dict[str, str]:
|
|
90
|
+
"""Build the ntfy publish headers for one notification.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
title: The notification title header.
|
|
94
|
+
click_url: The URL opened when the notification is tapped.
|
|
95
|
+
priority_name: The accepted priority name selecting the level header.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The header mapping ntfy reads to render and route the notification.
|
|
99
|
+
"""
|
|
100
|
+
return {
|
|
101
|
+
TITLE_HEADER_NAME: title,
|
|
102
|
+
PRIORITY_HEADER_NAME: ALL_PRIORITY_LEVELS_BY_NAME[priority_name],
|
|
103
|
+
CLICK_HEADER_NAME: click_url,
|
|
104
|
+
CONTENT_TYPE_HEADER_NAME: PLAIN_TEXT_CONTENT_TYPE,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def build_request(
|
|
108
|
+
title: str,
|
|
109
|
+
message: str,
|
|
110
|
+
click_url: str,
|
|
111
|
+
priority_name: str,
|
|
112
|
+
topic: str,
|
|
113
|
+
server: str,
|
|
114
|
+
) -> urllib.request.Request:
|
|
115
|
+
"""Build the ntfy publish request for one notification.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
title: The notification title header.
|
|
119
|
+
message: The notification body text.
|
|
120
|
+
click_url: The URL opened when the notification is tapped.
|
|
121
|
+
priority_name: The accepted priority name selecting the level header.
|
|
122
|
+
topic: The ntfy topic to publish to.
|
|
123
|
+
server: The ntfy server base URL.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
A POST request carrying the encoded body and the ntfy headers.
|
|
127
|
+
"""
|
|
128
|
+
endpoint_url = f"{server}{URL_PATH_SEPARATOR}{topic}"
|
|
129
|
+
return urllib.request.Request(
|
|
130
|
+
endpoint_url,
|
|
131
|
+
data=message.encode(MESSAGE_ENCODING),
|
|
132
|
+
headers=_build_headers(title, click_url, priority_name),
|
|
133
|
+
method=POST_METHOD_NAME,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def _send_notification(request: urllib.request.Request, timeout_seconds: int) -> None:
|
|
137
|
+
"""Send the ntfy request, raising on any transport failure.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
request: The publish request to send.
|
|
141
|
+
timeout_seconds: Seconds to wait before the POST times out.
|
|
142
|
+
"""
|
|
143
|
+
with urllib.request.urlopen(request, timeout=timeout_seconds):
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def _emit_error(message_text: str) -> None:
|
|
147
|
+
"""Write one error line to standard error."""
|
|
148
|
+
sys.stderr.write(message_text + "\n")
|
|
149
|
+
|
|
150
|
+
def _publish(
|
|
151
|
+
parsed_arguments: argparse.Namespace,
|
|
152
|
+
all_environment_variables: Mapping[str, str],
|
|
153
|
+
) -> int:
|
|
154
|
+
"""Publish the parsed notification, returning the process exit code."""
|
|
155
|
+
topic = resolve_topic(all_environment_variables)
|
|
156
|
+
if not topic:
|
|
157
|
+
_emit_error(
|
|
158
|
+
f"{TOPIC_ENVIRONMENT_VARIABLE_NAME} is unset; set it to the ntfy topic."
|
|
159
|
+
)
|
|
160
|
+
return 1
|
|
161
|
+
request = build_request(
|
|
162
|
+
parsed_arguments.title,
|
|
163
|
+
parsed_arguments.message,
|
|
164
|
+
parsed_arguments.click_url,
|
|
165
|
+
parsed_arguments.priority,
|
|
166
|
+
topic,
|
|
167
|
+
resolve_server(all_environment_variables),
|
|
168
|
+
)
|
|
169
|
+
try:
|
|
170
|
+
_send_notification(request, REQUEST_TIMEOUT_SECONDS)
|
|
171
|
+
except (urllib.error.URLError, OSError) as send_error:
|
|
172
|
+
_emit_error(f"ntfy publish failed: {send_error}")
|
|
173
|
+
return 1
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
def main(
|
|
177
|
+
all_arguments: list[str],
|
|
178
|
+
all_environment_variables: Mapping[str, str],
|
|
179
|
+
) -> int:
|
|
180
|
+
"""Publish one ntfy notification from the command line.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
all_arguments: The argument strings following the script name.
|
|
184
|
+
all_environment_variables: The environment mapping carrying topic and server.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Zero on a delivered notification; one on a missing topic or failed send.
|
|
188
|
+
"""
|
|
189
|
+
return _publish(parse_arguments(all_arguments), all_environment_variables)
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
raise SystemExit(main(sys.argv[1:], os.environ))
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Tests for notify_ntfy — argument parsing, payload shape, and delivery."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
5
|
+
|
|
6
|
+
from copilot_finding_triage_constants.config.notify_ntfy_constants import (
|
|
7
|
+
DEFAULT_PRIORITY_NAME,
|
|
8
|
+
DEFAULT_SERVER_URL,
|
|
9
|
+
SERVER_ENVIRONMENT_VARIABLE_NAME,
|
|
10
|
+
TOPIC_ENVIRONMENT_VARIABLE_NAME,
|
|
11
|
+
)
|
|
12
|
+
from notify_ntfy import (
|
|
13
|
+
build_request,
|
|
14
|
+
main,
|
|
15
|
+
parse_arguments,
|
|
16
|
+
resolve_server,
|
|
17
|
+
resolve_topic,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestParseArguments:
|
|
22
|
+
def test_reads_the_required_arguments(self) -> None:
|
|
23
|
+
parsed = parse_arguments(
|
|
24
|
+
["--title", "PR 743", "--message", "one concern", "--click-url", "u"]
|
|
25
|
+
)
|
|
26
|
+
assert parsed.title == "PR 743"
|
|
27
|
+
assert parsed.message == "one concern"
|
|
28
|
+
assert parsed.click_url == "u"
|
|
29
|
+
|
|
30
|
+
def test_priority_defaults_to_high(self) -> None:
|
|
31
|
+
parsed = parse_arguments(["--title", "t", "--message", "m", "--click-url", "u"])
|
|
32
|
+
assert parsed.priority == DEFAULT_PRIORITY_NAME
|
|
33
|
+
|
|
34
|
+
def test_rejects_an_unknown_priority(self) -> None:
|
|
35
|
+
try:
|
|
36
|
+
parse_arguments(
|
|
37
|
+
[
|
|
38
|
+
"--title",
|
|
39
|
+
"t",
|
|
40
|
+
"--message",
|
|
41
|
+
"m",
|
|
42
|
+
"--click-url",
|
|
43
|
+
"u",
|
|
44
|
+
"--priority",
|
|
45
|
+
"x",
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
except SystemExit as exit_error:
|
|
49
|
+
assert exit_error.code != 0
|
|
50
|
+
return
|
|
51
|
+
raise AssertionError("expected SystemExit for an unknown priority")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestResolveEnvironment:
|
|
55
|
+
def test_topic_reads_from_the_environment(self) -> None:
|
|
56
|
+
assert resolve_topic({TOPIC_ENVIRONMENT_VARIABLE_NAME: "converge-743"}) == (
|
|
57
|
+
"converge-743"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def test_topic_is_empty_when_unset(self) -> None:
|
|
61
|
+
assert resolve_topic({}) == ""
|
|
62
|
+
|
|
63
|
+
def test_server_falls_back_to_the_default(self) -> None:
|
|
64
|
+
assert resolve_server({}) == DEFAULT_SERVER_URL
|
|
65
|
+
|
|
66
|
+
def test_server_override_wins(self) -> None:
|
|
67
|
+
override = {SERVER_ENVIRONMENT_VARIABLE_NAME: "https://ntfy.example.com"}
|
|
68
|
+
assert resolve_server(override) == "https://ntfy.example.com"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestBuildRequest:
|
|
72
|
+
def test_builds_a_post_to_the_topic_endpoint(self) -> None:
|
|
73
|
+
request = build_request(
|
|
74
|
+
"PR 743", "body", "https://review", "high", "topic-a", DEFAULT_SERVER_URL
|
|
75
|
+
)
|
|
76
|
+
assert request.method == "POST"
|
|
77
|
+
assert request.full_url == "https://ntfy.sh/topic-a"
|
|
78
|
+
|
|
79
|
+
def test_carries_the_title_priority_and_click_headers(self) -> None:
|
|
80
|
+
request = build_request(
|
|
81
|
+
"PR 743", "body", "https://review", "urgent", "topic-a", DEFAULT_SERVER_URL
|
|
82
|
+
)
|
|
83
|
+
assert request.get_header("Title") == "PR 743"
|
|
84
|
+
assert request.get_header("Priority") == "5"
|
|
85
|
+
assert request.get_header("Click") == "https://review"
|
|
86
|
+
|
|
87
|
+
def test_encodes_the_message_as_the_body(self) -> None:
|
|
88
|
+
request = build_request(
|
|
89
|
+
"t", "the body text", "u", "high", "topic-a", DEFAULT_SERVER_URL
|
|
90
|
+
)
|
|
91
|
+
assert request.data == b"the body text"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class _RecordingHandler(BaseHTTPRequestHandler):
|
|
95
|
+
received_path: str = ""
|
|
96
|
+
received_body: bytes = b""
|
|
97
|
+
received_title: str = ""
|
|
98
|
+
|
|
99
|
+
def do_POST(self) -> None:
|
|
100
|
+
content_length = int(self.headers.get("Content-Length", "0"))
|
|
101
|
+
_RecordingHandler.received_path = self.path
|
|
102
|
+
_RecordingHandler.received_body = self.rfile.read(content_length)
|
|
103
|
+
_RecordingHandler.received_title = self.headers.get("Title", "")
|
|
104
|
+
self.send_response(200)
|
|
105
|
+
self.end_headers()
|
|
106
|
+
|
|
107
|
+
def log_message(self, format_string: str, *format_arguments: object) -> None:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestMainDelivers:
|
|
112
|
+
def test_missing_topic_returns_one(self) -> None:
|
|
113
|
+
exit_code = main(["--title", "t", "--message", "m", "--click-url", "u"], {})
|
|
114
|
+
assert exit_code == 1
|
|
115
|
+
|
|
116
|
+
def test_posts_the_notification_to_a_local_server(self) -> None:
|
|
117
|
+
server = HTTPServer(("127.0.0.1", 0), _RecordingHandler)
|
|
118
|
+
server_thread = threading.Thread(target=server.handle_request)
|
|
119
|
+
server_thread.start()
|
|
120
|
+
server_port = server.server_address[1]
|
|
121
|
+
server_url = f"http://127.0.0.1:{server_port}"
|
|
122
|
+
exit_code = main(
|
|
123
|
+
[
|
|
124
|
+
"--title",
|
|
125
|
+
"PR 743",
|
|
126
|
+
"--message",
|
|
127
|
+
"one code concern",
|
|
128
|
+
"--click-url",
|
|
129
|
+
"https://review",
|
|
130
|
+
],
|
|
131
|
+
{
|
|
132
|
+
TOPIC_ENVIRONMENT_VARIABLE_NAME: "converge-743",
|
|
133
|
+
SERVER_ENVIRONMENT_VARIABLE_NAME: server_url,
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
server_thread.join(timeout=5)
|
|
137
|
+
server.server_close()
|
|
138
|
+
assert exit_code == 0
|
|
139
|
+
assert _RecordingHandler.received_path == "/converge-743"
|
|
140
|
+
assert _RecordingHandler.received_body == b"one code concern"
|
|
141
|
+
assert _RecordingHandler.received_title == "PR 743"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ntfy Message Body
|
|
2
|
+
|
|
3
|
+
Fill this template, then pass the title as `--title` and the filled body as
|
|
4
|
+
`--message` to `scripts/notify_ntfy.py`. Pass the review URL as `--click-url` so
|
|
5
|
+
tapping the page opens the Copilot review.
|
|
6
|
+
|
|
7
|
+
## Title line
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Copilot code concern on PR {pr_number}: {short_pr_title}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Body
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
{finding_count} code concern(s) need your call on PR {pr_number} ({head_sha}):
|
|
17
|
+
|
|
18
|
+
- {file_1}:{line_1} — {severity_1} — {one_sentence_concern_1}
|
|
19
|
+
- {file_2}:{line_2} — {severity_2} — {one_sentence_concern_2}
|
|
20
|
+
|
|
21
|
+
Review: {review_url}
|
|
22
|
+
Reply within 45 minutes or the run tears down and reports these findings unreviewed.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Field notes
|
|
26
|
+
|
|
27
|
+
- `{finding_count}` counts the CODE CONCERN findings on this HEAD, not the
|
|
28
|
+
self-healing ones.
|
|
29
|
+
- Each bullet holds one finding: its `file:line`, its severity, and one sentence
|
|
30
|
+
naming the concern.
|
|
31
|
+
- `{review_url}` is the same URL passed as `--click-url`, so the reader sees it
|
|
32
|
+
in the body and reaches it by tapping the message.
|
|
33
|
+
- The 45-minute clock starts when the page reaches the user, which is the moment
|
|
34
|
+
`scripts/notify_ntfy.py` exits zero.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: copilot-review
|
|
3
3
|
description: >-
|
|
4
|
-
Spawns a background subagent that babysits the GitHub Copilot reviewer on the
|
|
5
|
-
PR: each
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'/copilot-review', 'watch copilot', 'babysit copilot review', 'keep re-requesting
|
|
9
|
-
copilot'.
|
|
4
|
+
Spawns a background subagent that babysits the GitHub Copilot reviewer on the
|
|
5
|
+
current PR: each tick it fixes unaddressed findings against HEAD, re-requests
|
|
6
|
+
review, and exits on convergence. Triggers: '/copilot-review', 'watch
|
|
7
|
+
copilot', 'babysit copilot review', 'keep re-requesting copilot'.
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
# Copilot Review
|
|
13
11
|
|
|
14
12
|
Delegates Copilot babysitting to a background subagent so the main session stays free. The subagent loops internally and closes itself on convergence.
|
|
15
13
|
|
|
14
|
+
## Transport check (before any GitHub step)
|
|
15
|
+
|
|
16
|
+
Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push` and take `true` as the pass. When any check fails, run the `pr-loop-cloud-transport` skill first, route every `gh` operation in this skill through its substitution matrix, and carry the same routing into the subagent prompt so the spawned watcher inherits it.
|
|
17
|
+
|
|
16
18
|
## When this skill applies
|
|
17
19
|
|
|
18
20
|
The user is on a PR branch, wants Copilot (the GitHub Copilot reviewer bot) to keep re-reviewing after each push, and wants findings auto-addressed between ticks — but does not want the main conversation consumed by polling.
|
package/skills/findbugs/SKILL.md
CHANGED
|
@@ -12,6 +12,10 @@ description: >-
|
|
|
12
12
|
|
|
13
13
|
**Core principle:** A clean-room bug audit on the entire pull request. The audit agent receives the PR diff and nothing else — no chat history, no prior framing, no implicit "we already looked at this." Independence is the point.
|
|
14
14
|
|
|
15
|
+
## Transport check (before any GitHub step)
|
|
16
|
+
|
|
17
|
+
Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push` and take `true` as the pass. When any check fails, run the `pr-loop-cloud-transport` skill first and route every `gh` operation in this skill through its substitution matrix.
|
|
18
|
+
|
|
15
19
|
## When this skill applies
|
|
16
20
|
|
|
17
21
|
User types `/findbugs` or asks for a bug audit on the current branch's PR. Typical moment: PR is up (draft or ready), and the user wants an independent second pair of eyes before merge or before requesting human review.
|
package/skills/fixbugs/SKILL.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fixbugs
|
|
3
3
|
description: >-
|
|
4
|
-
Fixes the bugs
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
argument filters by severity (e.g. /fixbugs P0, /fixbugs P0+P1).
|
|
9
|
-
Triggers: '/fixbugs', 'fix all the bugs', 'apply the audit fixes',
|
|
10
|
-
'implement the findbugs results'.
|
|
4
|
+
Fixes the bugs from the latest /findbugs run by handing them to /agent-prompt,
|
|
5
|
+
which spawns a background agent to apply every fix in one commit. An optional
|
|
6
|
+
argument filters by severity (e.g. /fixbugs P0). Triggers: '/fixbugs', 'fix
|
|
7
|
+
all the bugs', 'apply the audit fixes', 'apply the findbugs results'.
|
|
11
8
|
---
|
|
12
9
|
|
|
13
10
|
# Fixbugs
|
|
14
11
|
|
|
15
12
|
**Core principle:** A thin bridge between `/findbugs` (read-only audit) and `/agent-prompt` (structured prompt authoring + spawn). /fixbugs recovers the prior findings, packages them as a goal, and hands off. It does not author prompts itself, does not spawn agents directly, and does not run audits.
|
|
16
13
|
|
|
14
|
+
## Transport check (before any GitHub step)
|
|
15
|
+
|
|
16
|
+
Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push` and take `true` as the pass. When any check fails, run the `pr-loop-cloud-transport` skill first, route every `gh` operation in this skill through its substitution matrix, and carry the same routing into the goal string handed to `/agent-prompt` so the spawned fix agent inherits it.
|
|
17
|
+
|
|
17
18
|
## When this skill applies
|
|
18
19
|
|
|
19
20
|
Right after `/findbugs` returned findings on the current branch and the user wants the bugs fixed without further triage. Bare `/fixbugs` defaults to all severities (P0 + P1 + P2). Argument-filtered invocations (e.g. `/fixbugs P0`, `/fixbugs P0+P1`, `/fixbugs P0 P1`) narrow the target set.
|
package/skills/gotcha/CLAUDE.md
CHANGED
|
@@ -28,6 +28,6 @@ Each entry is a bullet under `## Gotchas`:
|
|
|
28
28
|
|
|
29
29
|
| Skill location | Target repo |
|
|
30
30
|
|---|---|
|
|
31
|
-
| `packages/claude-dev-env/skills/<name>/` | `jl-cmd/claude-
|
|
32
|
-
| `~/.claude/skills/<name>/` | `jl-cmd/claude-
|
|
31
|
+
| `packages/claude-dev-env/skills/<name>/` | `jl-cmd/claude-dev-env` |
|
|
32
|
+
| `~/.claude/skills/<name>/` | `jl-cmd/claude-dev-env` |
|
|
33
33
|
| Project `.claude/skills/<name>/` | That project's repo |
|