claude-dev-env 1.93.1 → 1.95.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.
Files changed (262) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +35 -27
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
  6. package/_shared/pr-loop/CLAUDE.md +1 -0
  7. package/_shared/pr-loop/audit-contract.md +1 -1
  8. package/_shared/pr-loop/gh-payloads.md +3 -3
  9. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  10. package/_shared/pr-loop/scripts/README.md +2 -2
  11. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -69
  13. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  14. package/_shared/pr-loop/state-schema.md +3 -22
  15. package/bin/CLAUDE.md +11 -2
  16. package/bin/ever-shipped-skills.mjs +70 -0
  17. package/bin/install.mjs +138 -13
  18. package/bin/install.prune.test.mjs +457 -0
  19. package/bin/install.test.mjs +10 -4
  20. package/docs/CODE_RULES.md +1 -1
  21. package/hooks/blocking/CLAUDE.md +2 -0
  22. package/hooks/blocking/code_rules_enforcer.py +4 -0
  23. package/hooks/blocking/code_rules_shared.py +82 -0
  24. package/hooks/blocking/code_rules_test_layout.py +9 -3
  25. package/hooks/blocking/pii_commit_command.py +398 -0
  26. package/hooks/blocking/pii_payload_scan.py +133 -0
  27. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  28. package/hooks/blocking/plain_language_blocker.py +138 -4
  29. package/hooks/blocking/sensitive_file_protector.py +114 -48
  30. package/hooks/blocking/tdd_enforcer.py +9 -2
  31. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  32. package/hooks/blocking/test_code_rules_shared.py +181 -0
  33. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  34. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  35. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  36. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  38. package/hooks/hooks_constants/CLAUDE.md +4 -2
  39. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  40. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  41. package/hooks/hooks_constants/local_identity.py +65 -15
  42. package/hooks/hooks_constants/pii_prevention_constants.py +16 -0
  43. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  44. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  45. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  46. package/hooks/hooks_constants/test_local_identity.py +68 -0
  47. package/hooks/pyproject.toml +75 -4
  48. package/hooks/validators/CLAUDE.md +1 -1
  49. package/hooks/validators/README.md +2 -0
  50. package/hooks/validators/python_style_checks.py +114 -136
  51. package/hooks/validators/python_style_helpers.py +95 -0
  52. package/hooks/validators/test_python_style_checks.py +0 -164
  53. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  54. package/hooks/validators/test_python_style_fixes.py +251 -0
  55. package/hooks/validators/test_python_style_helpers.py +125 -0
  56. package/package.json +1 -1
  57. package/rules/CLAUDE.md +1 -0
  58. package/rules/anti-corollary-tests.md +69 -0
  59. package/rules/bdd.md +1 -3
  60. package/rules/code-reviews.md +1 -1
  61. package/rules/gh-paginate.md +1 -1
  62. package/rules/plain-language.md +2 -0
  63. package/scripts/CLAUDE.md +10 -1
  64. package/scripts/Get-SessionAccount.ps1 +210 -0
  65. package/scripts/tests/CLAUDE.md +15 -1
  66. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  67. package/skills/CLAUDE.md +6 -27
  68. package/skills/anthropic-plan/SKILL.md +1 -13
  69. package/skills/autoconverge/CLAUDE.md +5 -2
  70. package/skills/autoconverge/SKILL.md +112 -387
  71. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  72. package/skills/autoconverge/reference/closing-report.md +9 -2
  73. package/skills/autoconverge/reference/convergence.md +26 -22
  74. package/skills/autoconverge/reference/headless-safety.md +44 -0
  75. package/skills/autoconverge/reference/multi-pr.md +88 -0
  76. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  77. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  78. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  79. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  80. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  81. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  82. package/skills/autoconverge/workflow/converge.mjs +286 -96
  83. package/skills/bugteam/CLAUDE.md +1 -1
  84. package/skills/bugteam/CONSTRAINTS.md +3 -3
  85. package/skills/bugteam/PROMPTS.md +8 -19
  86. package/skills/bugteam/SKILL.md +13 -14
  87. package/skills/bugteam/reference/CLAUDE.md +1 -1
  88. package/skills/bugteam/reference/README.md +2 -3
  89. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  90. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  91. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  92. package/skills/bugteam/reference/team-setup.md +11 -11
  93. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  94. package/skills/bugteam/scripts/README.md +20 -8
  95. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  96. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  97. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  98. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  99. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  100. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  101. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  102. package/skills/closeout/SKILL.md +153 -0
  103. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  104. package/skills/closeout/reference/issue-body-templates.md +108 -0
  105. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  106. package/skills/copilot-finding-triage/SKILL.md +80 -28
  107. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  108. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  109. package/skills/everything-search/SKILL.md +1 -2
  110. package/skills/grokify/SKILL.md +58 -0
  111. package/skills/grokify/templates/handoff-template.md +92 -0
  112. package/skills/orchestrator/SKILL.md +35 -25
  113. package/skills/orchestrator-refresh/SKILL.md +15 -11
  114. package/skills/pr-converge/CLAUDE.md +1 -1
  115. package/skills/pr-converge/SKILL.md +132 -233
  116. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  117. package/skills/pr-converge/reference/examples.md +37 -25
  118. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  119. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  120. package/skills/pr-converge/reference/per-tick.md +64 -41
  121. package/skills/pr-converge/reference/state-schema.md +27 -2
  122. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  123. package/skills/pr-converge/scripts/README.md +4 -3
  124. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  125. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  126. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  127. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  128. package/skills/pr-converge/scripts/conftest.py +8 -0
  129. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  130. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  131. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  132. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  133. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  134. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  135. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  136. package/skills/pr-fix-protocol/SKILL.md +20 -20
  137. package/skills/privacy-hygiene/SKILL.md +4 -3
  138. package/skills/reviewer-gates/SKILL.md +26 -8
  139. package/skills/session-log/SKILL.md +11 -12
  140. package/skills/session-tidy/SKILL.md +40 -30
  141. package/skills/skill-builder/CLAUDE.md +13 -10
  142. package/skills/skill-builder/SKILL.md +31 -31
  143. package/skills/skill-builder/references/CLAUDE.md +10 -6
  144. package/skills/skill-builder/references/delegation-map.md +13 -4
  145. package/skills/skill-builder/references/description-field.md +113 -0
  146. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  147. package/skills/skill-builder/references/skill-modularity.md +113 -0
  148. package/skills/skill-builder/references/skill-types.md +4 -0
  149. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  150. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  151. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  152. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  153. package/skills/skill-builder/workflows/new-skill.md +34 -10
  154. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  155. package/skills/team-advisor/SKILL.md +15 -11
  156. package/skills/usage-pause/SKILL.md +10 -14
  157. package/system-prompts/software-engineer.xml +7 -6
  158. package/hooks/validators/test_verify_paths.py +0 -32
  159. package/hooks/validators/verify_paths.py +0 -57
  160. package/skills/bdd-protocol/CLAUDE.md +0 -26
  161. package/skills/bdd-protocol/SKILL.md +0 -30
  162. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  163. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  164. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  165. package/skills/bg-agent/CLAUDE.md +0 -17
  166. package/skills/bg-agent/SKILL.md +0 -69
  167. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  168. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  169. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  170. package/skills/caveman/CLAUDE.md +0 -15
  171. package/skills/caveman/SKILL.md +0 -38
  172. package/skills/code/CLAUDE.md +0 -17
  173. package/skills/code/SKILL.md +0 -178
  174. package/skills/deep-research/CLAUDE.md +0 -17
  175. package/skills/deep-research/SKILL.md +0 -80
  176. package/skills/findbugs/CLAUDE.md +0 -20
  177. package/skills/findbugs/SKILL.md +0 -241
  178. package/skills/fixbugs/CLAUDE.md +0 -19
  179. package/skills/fixbugs/SKILL.md +0 -142
  180. package/skills/gh-paginate/CLAUDE.md +0 -18
  181. package/skills/gh-paginate/SKILL.md +0 -84
  182. package/skills/gotcha/CLAUDE.md +0 -33
  183. package/skills/gotcha/SKILL.md +0 -73
  184. package/skills/implement/CLAUDE.md +0 -27
  185. package/skills/implement/SKILL.md +0 -66
  186. package/skills/implement/scripts/CLAUDE.md +0 -22
  187. package/skills/implement/scripts/append_note.py +0 -133
  188. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  189. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  190. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  191. package/skills/implement/scripts/test_append_note.py +0 -191
  192. package/skills/log-audit/CLAUDE.md +0 -20
  193. package/skills/log-audit/SKILL.md +0 -66
  194. package/skills/log-audit/reference/CLAUDE.md +0 -9
  195. package/skills/log-audit/reference/charter.md +0 -52
  196. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  197. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  198. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  199. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  200. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  201. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  202. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  203. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  204. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  205. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  206. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  207. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  208. package/skills/logifix/CLAUDE.md +0 -36
  209. package/skills/logifix/SKILL.md +0 -69
  210. package/skills/logifix/scripts/CLAUDE.md +0 -16
  211. package/skills/logifix/scripts/logifix.ps1 +0 -205
  212. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  213. package/skills/monitor-open-prs/SKILL.md +0 -88
  214. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  215. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  216. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  217. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  218. package/skills/post-audit-findings/SKILL.md +0 -80
  219. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  220. package/skills/pr-consistency-audit/SKILL.md +0 -116
  221. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  222. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  223. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  224. package/skills/pr-review-responder/CLAUDE.md +0 -35
  225. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  226. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  227. package/skills/pr-review-responder/README.md +0 -168
  228. package/skills/pr-review-responder/SKILL.md +0 -203
  229. package/skills/pr-review-responder/TESTING.md +0 -350
  230. package/skills/pr-scope-resolve/SKILL.md +0 -45
  231. package/skills/pre-compact/CLAUDE.md +0 -24
  232. package/skills/pre-compact/SKILL.md +0 -134
  233. package/skills/qbug/CLAUDE.md +0 -40
  234. package/skills/qbug/SKILL.md +0 -387
  235. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  236. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  237. package/skills/refine/CLAUDE.md +0 -44
  238. package/skills/refine/SKILL.md +0 -262
  239. package/skills/refine/templates/CLAUDE.md +0 -17
  240. package/skills/refine/templates/implementation-notes-template.html +0 -56
  241. package/skills/refine/templates/plan-template.md +0 -60
  242. package/skills/research-mode/CLAUDE.md +0 -35
  243. package/skills/research-mode/SKILL.md +0 -53
  244. package/skills/structure-prompt/CLAUDE.md +0 -42
  245. package/skills/structure-prompt/SKILL.md +0 -44
  246. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  247. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  248. package/skills/structure-prompt/reference/block-classification.md +0 -27
  249. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  250. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  251. package/skills/structure-prompt/reference/cleanup.md +0 -33
  252. package/skills/structure-prompt/reference/constraints.md +0 -33
  253. package/skills/structure-prompt/reference/directives.md +0 -37
  254. package/skills/structure-prompt/reference/examples.md +0 -72
  255. package/skills/structure-prompt/reference/instantiation.md +0 -51
  256. package/skills/structure-prompt/reference/output-contract.md +0 -72
  257. package/skills/structure-prompt/reference/per-category.md +0 -23
  258. package/skills/structure-prompt/reference/persona.md +0 -38
  259. package/skills/structure-prompt/reference/research.md +0 -33
  260. package/skills/structure-prompt/reference/structure.md +0 -28
  261. package/skills/verified-build/CLAUDE.md +0 -33
  262. package/skills/verified-build/SKILL.md +0 -33
@@ -1,160 +0,0 @@
1
- """Tests for cluster_recurrences — signature grouping and timing regressions."""
2
-
3
- import io
4
- import sys
5
- from datetime import datetime, timedelta
6
- from pathlib import Path
7
-
8
- import pytest
9
-
10
- _script_directory = str(Path(__file__).resolve().parent)
11
- if _script_directory not in sys.path:
12
- sys.path.insert(0, _script_directory)
13
-
14
- from cluster_recurrences import ( # noqa: E402
15
- TimingSample,
16
- detect_timing_regressions,
17
- main,
18
- normalize_signature,
19
- recency_weight,
20
- rank_signature_clusters,
21
- )
22
- from collect_log_window import LogRecord # noqa: E402
23
-
24
-
25
- def _record(timestamp: datetime, message: str) -> LogRecord:
26
- return LogRecord(
27
- timestamp=timestamp, source="a_hook", level="block", message=message
28
- )
29
-
30
-
31
- class TestNormalizeSignature:
32
- def test_two_messages_differing_only_in_numbers_share_a_signature(self) -> None:
33
- first = normalize_signature("blocked at line 12 with 3 issues")
34
- second = normalize_signature("blocked at line 348 with 9 issues")
35
- assert first == second
36
-
37
- def test_messages_differing_only_in_a_path_share_a_signature(self) -> None:
38
- first = normalize_signature(r"cannot read C:\a\b\one.py right now")
39
- second = normalize_signature(r"cannot read C:\x\y\two.py right now")
40
- assert first == second
41
-
42
- def test_messages_differing_only_in_a_hash_share_a_signature(self) -> None:
43
- first = normalize_signature("commit a1b2c3d failed to apply")
44
- second = normalize_signature("commit deadbeef failed to apply")
45
- assert first == second
46
-
47
- def test_distinct_messages_keep_distinct_signatures(self) -> None:
48
- assert normalize_signature("heavy word detected") != normalize_signature(
49
- "missing type hint"
50
- )
51
-
52
-
53
- class TestRecencyWeight:
54
- def test_a_fresh_record_weighs_about_one(self) -> None:
55
- now = datetime(2026, 7, 3, 12, 0, 0)
56
- weight = recency_weight(now, now)
57
- assert abs(weight - 1.0) < 0.001
58
-
59
- def test_a_record_one_half_life_old_weighs_about_the_decay_base(self) -> None:
60
- now = datetime(2026, 7, 3, 12, 0, 0)
61
- one_half_life_ago = now - timedelta(hours=24)
62
- weight = recency_weight(one_half_life_ago, now)
63
- assert abs(weight - 0.5) < 0.001
64
-
65
- def test_a_future_record_is_clamped_to_at_most_one(self) -> None:
66
- now = datetime(2026, 7, 3, 12, 0, 0)
67
- one_hour_ahead = now + timedelta(hours=1)
68
- weight = recency_weight(one_hour_ahead, now)
69
- assert abs(weight - 1.0) < 0.001
70
-
71
-
72
- class TestRankSignatureClusters:
73
- def test_the_most_frequent_recent_signature_ranks_first(self) -> None:
74
- now = datetime(2026, 7, 3, 12, 0, 0)
75
- records = [
76
- _record(now - timedelta(hours=1), "blocked at line 1"),
77
- _record(now - timedelta(hours=2), "blocked at line 2"),
78
- _record(now - timedelta(hours=1), "blocked at line 3"),
79
- _record(now - timedelta(hours=200), "a rare stale failure"),
80
- ]
81
- clusters = rank_signature_clusters(records, now)
82
- assert clusters[0].count == 3
83
- assert clusters[0].signature == normalize_signature("blocked at line 1")
84
-
85
- def test_each_distinct_signature_becomes_one_cluster(self) -> None:
86
- now = datetime(2026, 7, 3, 12, 0, 0)
87
- records = [
88
- _record(now, "heavy word here"),
89
- _record(now, "missing type hint"),
90
- ]
91
- clusters = rank_signature_clusters(records, now)
92
- assert len(clusters) == 2
93
-
94
-
95
- class TestDetectTimingRegressions:
96
- def test_flags_an_operation_whose_recent_runs_are_slower(self) -> None:
97
- base = datetime(2026, 7, 3, 0, 0, 0)
98
- samples = [
99
- TimingSample("extract", base + timedelta(minutes=1), 100.0),
100
- TimingSample("extract", base + timedelta(minutes=2), 110.0),
101
- TimingSample("extract", base + timedelta(minutes=3), 105.0),
102
- TimingSample("extract", base + timedelta(minutes=40), 300.0),
103
- TimingSample("extract", base + timedelta(minutes=41), 320.0),
104
- TimingSample("extract", base + timedelta(minutes=42), 310.0),
105
- ]
106
- regressions = detect_timing_regressions(samples)
107
- assert len(regressions) == 1
108
- assert regressions[0].operation == "extract"
109
- assert regressions[0].ratio > 1.5
110
-
111
- def test_ignores_a_steady_operation(self) -> None:
112
- base = datetime(2026, 7, 3, 0, 0, 0)
113
- samples = [
114
- TimingSample("steady", base + timedelta(minutes=each_minute_offset), 100.0)
115
- for each_minute_offset in range(6)
116
- ]
117
- assert detect_timing_regressions(samples) == []
118
-
119
- def test_ignores_an_operation_with_too_few_samples(self) -> None:
120
- base = datetime(2026, 7, 3, 0, 0, 0)
121
- samples = [
122
- TimingSample("sparse", base + timedelta(minutes=1), 100.0),
123
- TimingSample("sparse", base + timedelta(minutes=2), 900.0),
124
- ]
125
- assert detect_timing_regressions(samples) == []
126
-
127
-
128
- class TestMain:
129
- def test_rejects_non_json_stdin(
130
- self,
131
- monkeypatch: pytest.MonkeyPatch,
132
- capsys: pytest.CaptureFixture[str],
133
- ) -> None:
134
- monkeypatch.setattr(sys, "stdin", io.StringIO("not json"))
135
- exit_code = main()
136
- captured = capsys.readouterr()
137
- assert exit_code == 1
138
- assert "not valid JSON" in captured.err
139
-
140
- def test_rejects_a_non_list_payload(
141
- self,
142
- monkeypatch: pytest.MonkeyPatch,
143
- capsys: pytest.CaptureFixture[str],
144
- ) -> None:
145
- monkeypatch.setattr(sys, "stdin", io.StringIO('{"not": "a list"}'))
146
- exit_code = main()
147
- captured = capsys.readouterr()
148
- assert exit_code == 1
149
- assert "must be a list" in captured.err
150
-
151
- def test_rejects_a_malformed_record(
152
- self,
153
- monkeypatch: pytest.MonkeyPatch,
154
- capsys: pytest.CaptureFixture[str],
155
- ) -> None:
156
- monkeypatch.setattr(sys, "stdin", io.StringIO('[{"missing": "fields"}]'))
157
- exit_code = main()
158
- captured = capsys.readouterr()
159
- assert exit_code == 1
160
- assert "malformed record" in captured.err
@@ -1,111 +0,0 @@
1
- """Tests for collect_log_window — parsing and windowing hook-block records."""
2
-
3
- import json
4
- import sys
5
- from datetime import datetime, timedelta
6
- from pathlib import Path
7
-
8
- _script_directory = str(Path(__file__).resolve().parent)
9
- if _script_directory not in sys.path:
10
- sys.path.insert(0, _script_directory)
11
-
12
- from collect_log_window import ( # noqa: E402
13
- LogRecord,
14
- collect_records,
15
- parse_log_line,
16
- read_log_window,
17
- )
18
-
19
-
20
- def _block_line(timestamp_text: str, hook_name: str, reason_text: str) -> str:
21
- return json.dumps(
22
- {
23
- "timestamp": timestamp_text,
24
- "hook": hook_name,
25
- "event": "PreToolUse",
26
- "reason": reason_text,
27
- }
28
- )
29
-
30
-
31
- class TestParseLogLine:
32
- def test_parses_a_well_formed_block_record(self) -> None:
33
- line = _block_line(
34
- "2026-07-03T09:00:00", "plain_language_blocker", "Heavy word"
35
- )
36
- record = parse_log_line(line)
37
- assert record == LogRecord(
38
- timestamp=datetime(2026, 7, 3, 9, 0, 0),
39
- source="plain_language_blocker",
40
- level="block",
41
- message="Heavy word",
42
- )
43
-
44
- def test_returns_none_for_a_blank_line(self) -> None:
45
- assert parse_log_line(" ") is None
46
-
47
- def test_returns_none_for_non_json(self) -> None:
48
- assert parse_log_line("not json at all") is None
49
-
50
- def test_returns_none_when_reason_is_missing(self) -> None:
51
- line = json.dumps({"timestamp": "2026-07-03T09:00:00", "hook": "x"})
52
- assert parse_log_line(line) is None
53
-
54
- def test_returns_none_for_an_unparseable_timestamp(self) -> None:
55
- line = _block_line("not-a-timestamp", "x", "y")
56
- assert parse_log_line(line) is None
57
-
58
-
59
- class TestCollectRecords:
60
- def test_keeps_only_records_at_or_after_the_window_start(self) -> None:
61
- window_start = datetime(2026, 7, 3, 8, 0, 0)
62
- log_text = "\n".join(
63
- [
64
- _block_line("2026-07-03T07:59:59", "old_hook", "before window"),
65
- _block_line("2026-07-03T08:00:00", "edge_hook", "on the boundary"),
66
- _block_line("2026-07-03T09:30:00", "new_hook", "inside window"),
67
- ]
68
- )
69
- records = collect_records(log_text, window_start)
70
- assert [each_record.source for each_record in records] == [
71
- "edge_hook",
72
- "new_hook",
73
- ]
74
-
75
- def test_skips_malformed_lines(self) -> None:
76
- window_start = datetime(2026, 7, 3, 0, 0, 0)
77
- log_text = "\n".join(
78
- [
79
- "garbage",
80
- _block_line("2026-07-03T09:00:00", "good_hook", "kept"),
81
- "",
82
- ]
83
- )
84
- records = collect_records(log_text, window_start)
85
- assert len(records) == 1
86
- assert records[0].source == "good_hook"
87
-
88
-
89
- class TestReadLogWindow:
90
- def test_returns_empty_list_when_the_log_is_absent(self, tmp_path: Path) -> None:
91
- missing_path = tmp_path / "hook-blocks.log"
92
- assert read_log_window(missing_path, 24, datetime(2026, 7, 3, 12, 0, 0)) == []
93
-
94
- def test_reads_records_inside_the_window_from_a_real_file(
95
- self, tmp_path: Path
96
- ) -> None:
97
- now = datetime(2026, 7, 3, 12, 0, 0)
98
- recent_time = (now - timedelta(hours=1)).isoformat()
99
- stale_time = (now - timedelta(hours=48)).isoformat()
100
- log_path = tmp_path / "hook-blocks.log"
101
- log_path.write_text(
102
- "\n".join(
103
- [
104
- _block_line(stale_time, "stale_hook", "too old"),
105
- _block_line(recent_time, "recent_hook", "in window"),
106
- ]
107
- ),
108
- encoding="utf-8",
109
- )
110
- records = read_log_window(log_path, 24, now)
111
- assert [each_record.source for each_record in records] == ["recent_hook"]
@@ -1,126 +0,0 @@
1
- """Tests for mine_copilot_findings — defect classification and clustering."""
2
-
3
- import sys
4
- from pathlib import Path
5
-
6
- _script_directory = str(Path(__file__).resolve().parent)
7
- if _script_directory not in sys.path:
8
- sys.path.insert(0, _script_directory)
9
-
10
- from mine_copilot_findings import ( # noqa: E402
11
- ReviewerComment,
12
- _flatten_pages,
13
- _pull_number_from_url,
14
- _reviewer_comment_from_payload,
15
- classify_defect,
16
- cluster_defects,
17
- proposal_for_defect_class,
18
- )
19
- from log_audit_constants.mine_copilot_findings_constants import ( # noqa: E402
20
- PROPOSAL_BY_DEFECT_CLASS,
21
- )
22
-
23
-
24
- class TestClassifyDefect:
25
- def test_recognizes_a_missing_type_hint_comment(self) -> None:
26
- assert (
27
- classify_defect("This function is missing a type hint on the return")
28
- == "missing-type-hint"
29
- )
30
-
31
- def test_recognizes_a_broad_except_comment(self) -> None:
32
- assert classify_defect("There is a broad except swallowing errors") == (
33
- "broad-except"
34
- )
35
-
36
- def test_recognizes_a_magic_value_comment(self) -> None:
37
- assert classify_defect("42 is a magic number, extract it") == "magic-value"
38
-
39
- def test_returns_none_for_praise(self) -> None:
40
- assert classify_defect("Looks great, nicely done!") is None
41
-
42
-
43
- class TestClusterDefects:
44
- def test_groups_comments_by_class_and_ranks_by_count(self) -> None:
45
- comments = [
46
- ReviewerComment(1, "cursor[bot]", "missing type hint here"),
47
- ReviewerComment(2, "cursor[bot]", "another missing annotation"),
48
- ReviewerComment(3, "cursor[bot]", "this is a broad except"),
49
- ReviewerComment(4, "cursor[bot]", "nice work"),
50
- ]
51
- clusters = cluster_defects(comments)
52
- assert clusters[0].defect_class == "missing-type-hint"
53
- assert clusters[0].count == 2
54
- assert len(clusters) == 2
55
-
56
- def test_caps_the_number_of_example_bodies(self) -> None:
57
- comments = [
58
- ReviewerComment(
59
- each_index, "cursor[bot]", f"missing type hint number {each_index}"
60
- )
61
- for each_index in range(10)
62
- ]
63
- clusters = cluster_defects(comments)
64
- assert clusters[0].count == 10
65
- assert len(clusters[0].example_bodies) == 3
66
-
67
-
68
- class TestProposalForDefectClass:
69
- def test_maps_a_class_to_its_skill_edit_proposal(self) -> None:
70
- proposal = proposal_for_defect_class("broad-except")
71
- assert proposal.defect_class == "broad-except"
72
- assert proposal.proposal == PROPOSAL_BY_DEFECT_CLASS["broad-except"]
73
-
74
-
75
- class TestPullNumberFromUrl:
76
- def test_reads_the_trailing_pull_number(self) -> None:
77
- url = "https://api.github.com/repos/owner/name/pulls/867"
78
- assert _pull_number_from_url(url) == 867
79
-
80
- def test_returns_none_when_the_last_segment_is_not_a_number(self) -> None:
81
- assert _pull_number_from_url("https://api.github.com/repos/owner/name") is None
82
-
83
-
84
- class TestReviewerCommentFromPayload:
85
- def test_builds_a_comment_from_a_reviewer_bot_payload(self) -> None:
86
- payload = {
87
- "user": {"login": "cursor[bot]"},
88
- "body": "missing type hint",
89
- "pull_request_url": "https://api.github.com/repos/owner/name/pulls/12",
90
- }
91
- parsed_comment = _reviewer_comment_from_payload(payload)
92
- assert parsed_comment == ReviewerComment(
93
- pull_number=12, author="cursor[bot]", body="missing type hint"
94
- )
95
-
96
- def test_skips_a_comment_from_a_non_reviewer_login(self) -> None:
97
- payload = {
98
- "user": {"login": "some-human"},
99
- "body": "looks good",
100
- "pull_request_url": "https://api.github.com/repos/owner/name/pulls/12",
101
- }
102
- assert _reviewer_comment_from_payload(payload) is None
103
-
104
- def test_skips_a_payload_that_is_not_a_dict(self) -> None:
105
- assert _reviewer_comment_from_payload("not a payload") is None
106
-
107
- def test_skips_a_payload_missing_the_pull_request_url(self) -> None:
108
- payload = {"user": {"login": "cursor[bot]"}, "body": "missing type hint"}
109
- assert _reviewer_comment_from_payload(payload) is None
110
-
111
- def test_skips_a_payload_whose_url_has_no_numeric_pull_number(self) -> None:
112
- payload = {
113
- "user": {"login": "cursor[bot]"},
114
- "body": "missing type hint",
115
- "pull_request_url": "https://api.github.com/repos/owner/name/pulls",
116
- }
117
- assert _reviewer_comment_from_payload(payload) is None
118
-
119
-
120
- class TestFlattenPages:
121
- def test_flattens_multiple_pages_into_one_list(self) -> None:
122
- slurped_payload = [[{"id": 1}, {"id": 2}], [{"id": 3}]]
123
- assert _flatten_pages(slurped_payload) == [{"id": 1}, {"id": 2}, {"id": 3}]
124
-
125
- def test_returns_empty_list_for_a_non_list_payload(self) -> None:
126
- assert _flatten_pages({"message": "not a list"}) == []
@@ -1,36 +0,0 @@
1
- # logifix skill
2
-
3
- Restores the Logitech Gaming Software (LCore) tray icon when it disappears on Windows. Runs a PowerShell script that reproduces the verified recovery procedure.
4
-
5
- **Trigger:** `/logifix`, "logitech tray icon missing", "LCore tray gone", "logitech is not loaded".
6
-
7
- ## Key files
8
-
9
- | File | Purpose |
10
- |---|---|
11
- | `SKILL.md` | Full procedure description, invocation options, fallback steps |
12
- | `scripts/logifix.ps1` | PowerShell script that runs the recovery procedure |
13
-
14
- ## Subdirectories
15
-
16
- | Directory | Role |
17
- |---|---|
18
- | `scripts/` | The PowerShell recovery script |
19
-
20
- ## What the script does
21
-
22
- 1. Takes a state snapshot (Explorer instances, Logitech services, LCore process).
23
- 2. Stops LCore in user context.
24
- 3. Runs one elevated UAC step: starts `LogiRegistryService`, stops `explorer.exe`, and lets Windows shell auto-respawn rebuild it (does **not** call `Start-Process explorer` from the elevated block).
25
- 4. Waits for shell auto-respawn (default 5 seconds).
26
- 5. Confirms exactly one Explorer in the user's session.
27
- 6. Runs `LCoreRelaunchAttemptCount` full stop-then-launch cycles of `LCore.exe /minimized` (default 2). Always runs the full count.
28
- 7. Takes a final state snapshot.
29
-
30
- ## Invocation
31
-
32
- ```
33
- /logifix
34
- ```
35
-
36
- Optional parameters: `-ExplorerAutoRespawnWaitSeconds`, `-LCoreInitializationWaitSeconds`, `-LCoreRelaunchAttemptCount`.
@@ -1,69 +0,0 @@
1
- ---
2
- name: logifix
3
- description: Restore the Logitech Gaming Software (LCore) tray icon when it disappears on Windows. Calls a PowerShell script that reproduces the verified Session 2 recovery procedure from 2026-04-25. Triggers on "logifix", "/logifix", "logitech tray icon missing", "LCore tray gone", "logitech is not loaded".
4
- ---
5
-
6
- # logifix
7
-
8
- ## Overview
9
-
10
- Restore the Logitech Gaming Software (LCore) tray icon by reproducing the verified recovery procedure documented in `sessions/System Support/2. Logitech Tray Icon Fix Recurrence.md`.
11
-
12
- **Announce at start:** "Running /logifix to restore the LCore tray icon."
13
-
14
- ## What the script does
15
-
16
- `scripts/logifix.ps1` runs this sequence (verified during Session 2 on 2026-04-25):
17
-
18
- 1. **State snapshot.** Explorer instances per session, Logitech services state, LCore process state.
19
- 2. **Stop LCore in user context.** `Stop-Process -Name LCore -Force`.
20
- 3. **Single elevated UAC step:**
21
- - `Start-Service -Name LogiRegistryService` (handles the recurrence case where the service is Stopped).
22
- - `Stop-Process -Name explorer -Force`.
23
- - **Does NOT** call `Start-Process explorer` from inside the elevated block. Windows shell auto-respawn handles the user-session explorer cleanly. Skipping this line is the operative fix discovered in Session 2 — including it created a duplicate admin-context `explorer.exe` (no resolvable owner, parent = the admin pwsh) that blocked LCore tray registration.
24
- 4. **Wait for Windows shell auto-respawn** (default 5 seconds).
25
- 5. **Verify exactly one explorer in the user's session.**
26
- 6. **Perform `LCoreRelaunchAttemptCount` full stop-then-launch cycles of `LCore.exe /minimized`** (default 2). The full count always runs — a responsive LCore on the first attempt does NOT imply the tray icon registered. Per Session 2 gotcha #2, the first relaunch can leave LCore responsive with no tray icon; only a second stop+launch reliably triggers `Shell_NotifyIcon` registration.
27
- 7. **Final state snapshot.**
28
-
29
- ## Invocation
30
-
31
- From Claude Code:
32
-
33
- ```
34
- /logifix
35
- ```
36
-
37
- Direct PowerShell:
38
-
39
- ```
40
- pwsh -File "$HOME\.claude\skills\logifix\scripts\logifix.ps1"
41
- ```
42
-
43
- Optional parameters (all have safe defaults):
44
-
45
- - `-ExplorerAutoRespawnWaitSeconds <int>` — wait after the elevated kill (default 5).
46
- - `-LCoreInitializationWaitSeconds <int>` — wait after each LCore relaunch before checking responsiveness (default 5).
47
- - `-LCoreRelaunchAttemptCount <int>` — number of LCore stop+launch cycles to perform (default 2). Always runs the full count.
48
-
49
- ## When to use
50
-
51
- - LCore tray icon missing (also confirmed absent from the overflow `^` chevron).
52
- - LCore process is running but the tray icon never appears.
53
- - After resume from sleep, system restart, or a Logitech service crash that leaves LCore in a half-loaded state.
54
-
55
- ## Fallback (if the script does not restore the icon)
56
-
57
- If `/logifix` reports that UAC was canceled, or LCore is still not responding after both relaunch cycles:
58
-
59
- 1. **Ctrl+Shift+Esc** → Task Manager.
60
- 2. Find **Windows Explorer** → right-click → **Restart**.
61
- 3. Re-run `/logifix`.
62
-
63
- The Task Manager restart path is guaranteed to hit the correct interactive session and elevation context regardless of how the calling shell was launched. In Session 2, elevated calls from Claude Code's PowerShell tool could not be confirmed to reach the user's interactive Session 1 — Task Manager bypasses that ambiguity.
64
-
65
- ## Source
66
-
67
- Procedure verified during Session 2 (Logitech Tray Icon Fix Recurrence), 2026-04-25. The session log lists the verified command set, the gotcha catalog, and the final process/service state.
68
-
69
- The "always run the full relaunch count" behavior was added on 2026-04-26 after `/logifix` reported success on the first responsive attempt but the tray icon never appeared — the original early-break optimization conflicted with documented Session 2 gotcha #2 (responsive LCore, no tray icon).
@@ -1,16 +0,0 @@
1
- # logifix/scripts
2
-
3
- PowerShell recovery script for the `logifix` skill.
4
-
5
- ## Key files
6
-
7
- | File | Purpose |
8
- |---|---|
9
- | `logifix.ps1` | Restores the LCore tray icon: stops LCore, runs one elevated UAC step (starts `LogiRegistryService`, stops explorer, lets Windows auto-respawn), then re-launches LCore a configurable number of times |
10
-
11
- ## Conventions
12
-
13
- - The script accepts three optional parameters: `-ExplorerAutoRespawnWaitSeconds` (default 5), `-LCoreInitializationWaitSeconds` (default 5), `-LCoreRelaunchAttemptCount` (default 2).
14
- - The full relaunch count always runs — an early-exit on a responsive LCore is intentional behavior per the recovery procedure.
15
- - The elevated block does **not** call `Start-Process explorer`; Windows shell auto-respawn handles that to avoid a duplicate admin-context Explorer.
16
- - Run directly as: `pwsh -File "$HOME\.claude\skills\logifix\scripts\logifix.ps1"`.