claude-dev-env 2.11.0 → 2.13.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 (203) hide show
  1. package/_shared/advisor/reference/sol-rung.md +5 -2
  2. package/_shared/advisor/reference/warm-up.md +1 -0
  3. package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
  4. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
  5. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  7. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  8. package/agents/CLAUDE.md +1 -2
  9. package/agents/pr-description-writer.md +11 -81
  10. package/bin/install.mjs +48 -9
  11. package/bin/install.prune.test.mjs +26 -0
  12. package/bin/install.settings-defaults.test.mjs +60 -33
  13. package/bin/install.test.mjs +2 -4
  14. package/commands/CLAUDE.md +1 -0
  15. package/commands/sr-loop.md +52 -0
  16. package/docs/CLAUDE.md +0 -1
  17. package/docs/references/CLAUDE.md +0 -1
  18. package/hooks/CLAUDE.md +1 -1
  19. package/hooks/blocking/CLAUDE.md +2 -27
  20. package/hooks/blocking/config/CLAUDE.md +1 -13
  21. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  22. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  23. package/hooks/blocking/stop_dispatcher.py +4 -4
  24. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  25. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  26. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  27. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  28. package/hooks/diagnostic/CLAUDE.md +3 -3
  29. package/hooks/diagnostic/hook_log_extractor.py +2 -36
  30. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  31. package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
  32. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  33. package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
  34. package/hooks/git-hooks/pre_push.py +3 -85
  35. package/hooks/git-hooks/test_pre_push.py +0 -130
  36. package/hooks/hooks.json +0 -35
  37. package/hooks/hooks_constants/CLAUDE.md +0 -9
  38. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  39. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  40. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  41. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  42. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  43. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  44. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  45. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  46. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  47. package/hooks/lifecycle/CLAUDE.md +1 -3
  48. package/package.json +1 -1
  49. package/rules/CLAUDE.md +0 -2
  50. package/rules/gh-cli-conventions.md +0 -1
  51. package/rules/git-workflow.md +1 -3
  52. package/scripts/CLAUDE.md +0 -1
  53. package/scripts/_code_review_test_support.py +0 -95
  54. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  55. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  56. package/scripts/invoke_code_review.py +8 -451
  57. package/scripts/test_invoke_code_review.py +5 -61
  58. package/scripts/test_invoke_code_review_cli.py +1 -45
  59. package/scripts/test_invoke_code_review_contract.py +1 -118
  60. package/settings.json +1 -10
  61. package/skills/CLAUDE.md +8 -0
  62. package/skills/autoconverge/reference/CLAUDE.md +1 -1
  63. package/skills/autoconverge/reference/convergence.md +7 -7
  64. package/skills/autoconverge/reference/gotchas.md +3 -10
  65. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  66. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  67. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  68. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  69. package/skills/autoconverge/workflow/converge.mjs +201 -238
  70. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  71. package/skills/bugteam/CONSTRAINTS.md +1 -1
  72. package/skills/bugteam/SKILL.md +9 -0
  73. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  74. package/skills/bugteam/test_skill_additions.py +9 -0
  75. package/skills/comments/SKILL.md +65 -0
  76. package/skills/descriptions/SKILL.md +51 -0
  77. package/skills/emergencies/SKILL.md +42 -0
  78. package/skills/imagegen/SKILL.md +21 -0
  79. package/skills/imagegen/scripts/config/__init__.py +1 -0
  80. package/skills/imagegen/scripts/config/constants.py +28 -0
  81. package/skills/imagegen/scripts/imagegen.py +45 -0
  82. package/skills/imagegen/scripts/imagegen_core.py +528 -0
  83. package/skills/imagegen/scripts/test_imagegen.py +204 -0
  84. package/skills/orchestrator/SKILL.md +2 -4
  85. package/skills/plan-to-pr/SKILL.md +6 -7
  86. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  87. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  88. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  89. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  90. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  91. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  92. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  93. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  94. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  95. package/skills/pr-converge/SKILL.md +13 -16
  96. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  97. package/skills/pr-converge/reference/state-schema.md +1 -27
  98. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  99. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  100. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  101. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  102. package/skills/prototype/SKILL.md +4 -4
  103. package/skills/prototype/reference/honest-limitations.md +4 -4
  104. package/skills/prototype/reference/promotion-tasks.md +2 -2
  105. package/skills/prototype/workflows/promotion.md +4 -6
  106. package/skills/reviews/SKILL.md +85 -0
  107. package/skills/small-cl/SKILL.md +52 -0
  108. package/skills/usage-pause/SKILL.md +1 -1
  109. package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
  110. package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
  111. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
  112. package/agents/code-verifier.md +0 -74
  113. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  114. package/docs/references/code-review-enforcement.md +0 -132
  115. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  116. package/hooks/blocking/code_review_gate_deny.py +0 -74
  117. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  118. package/hooks/blocking/code_review_push_gate.py +0 -145
  119. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  120. package/hooks/blocking/code_review_stamp_store.py +0 -233
  121. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  122. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  123. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  124. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  125. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  126. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  127. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  128. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  129. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  130. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  131. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  132. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  133. package/hooks/blocking/conftest.py +0 -123
  134. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  135. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  136. package/hooks/blocking/pr_description_body_audit.py +0 -148
  137. package/hooks/blocking/pr_description_command_parser.py +0 -237
  138. package/hooks/blocking/pr_description_enforcer.py +0 -227
  139. package/hooks/blocking/pr_description_pr_number.py +0 -155
  140. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  141. package/hooks/blocking/pr_description_readability.py +0 -364
  142. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  143. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  144. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  145. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  146. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  147. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  148. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  149. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  150. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  151. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  152. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  153. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  154. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  155. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  156. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  157. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  158. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  159. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  160. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  161. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  162. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  163. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  164. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  165. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  166. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  167. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  168. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  169. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  170. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  171. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  172. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  173. package/hooks/blocking/verification_verdict_store.py +0 -1039
  174. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  175. package/hooks/blocking/verified_commit_gate.py +0 -113
  176. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  177. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  178. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  179. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  180. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  181. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  182. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  183. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  184. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  185. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  186. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  187. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  188. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  189. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  190. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  191. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  192. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  193. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  194. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  195. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  196. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  197. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  198. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  199. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  200. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  201. package/rules/proof-of-work-pr-comments.md +0 -22
  202. package/rules/verified-commit-gate-skip.md +0 -4
  203. package/scripts/Show-Asset.ps1 +0 -106
@@ -36,8 +36,6 @@ _hooks_dir = str(Path(__file__).resolve().parent.parent)
36
36
  if _hooks_dir not in sys.path:
37
37
  sys.path.insert(0, _hooks_dir)
38
38
 
39
- from verification_verdict_store import resolve_repo_root # noqa: E402
40
-
41
39
  from blocking._gh_body_arg_utils import ( # noqa: E402
42
40
  all_value_flags,
43
41
  count_extra_tokens_to_skip_for_split_quoted_value,
@@ -68,6 +66,7 @@ from hooks_constants.conventional_pr_title_gate_constants import ( # noqa: E402
68
66
  YAML_LIST_ITEM_PREFIX,
69
67
  )
70
68
  from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
69
+ from hooks_constants.project_paths_reader import find_git_root as resolve_repo_root # noqa: E402
71
70
 
72
71
 
73
72
  def _matches_gh_pr_title_subcommand(command: str) -> bool:
@@ -3,7 +3,7 @@
3
3
 
4
4
  SendUserFile attaches a file to the session. While the user is at the terminal
5
5
  (status "normal" or unset) an attach does not let them see the file — it must
6
- open on screen in its own viewer via Show-Asset.ps1. The one attach allowed
6
+ open on screen in its native Windows app. The one attach allowed
7
7
  through is an away-from-desk phone push (status "proactive").
8
8
  """
9
9
 
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env python3
2
- """Stop-hook dispatcher that hosts the six Stop-chain hooks in one process.
2
+ """Stop-hook dispatcher that hosts the Stop-chain blockers in one process.
3
3
 
4
4
  Reads the Stop payload from stdin once, runs each hosted hook in registration
5
5
  order via the shared hosted-hook runner, and emits the first block decision the
6
- chain produced. Later hooks still run so side-effect hooks (the log extractor
7
- wrapper) fire even when an earlier hook blocked. A single hosted hook crash
8
- fails open and does not suppress the remaining hooks.
6
+ chain produced. Later hooks still run after an earlier block so every hosted
7
+ hook gets a chance to run. A single hosted hook crash fails open and does not
8
+ suppress the remaining hooks.
9
9
  """
10
10
 
11
11
  from __future__ import annotations
@@ -221,9 +221,6 @@ def test_powershell_selects_the_shared_hooks_in_registration_order() -> None:
221
221
  assert powershell_paths == [
222
222
  "blocking/unscoped_search_blocker.py",
223
223
  "blocking/pii_prevention_blocker.py",
224
- "blocking/code_review_push_gate.py",
225
- "blocking/code_review_pr_create_gate.py",
226
- "blocking/code_review_stamp_directory_write_blocker.py",
227
224
  ]
228
225
 
229
226
 
@@ -715,8 +715,8 @@ def test_dispatcher_write_applies_both_groups() -> None:
715
715
  assert "blocking/plain_language_blocker.py" in all_write_script_paths, (
716
716
  "plain_language_blocker (Group B) must be in Write applicable set"
717
717
  )
718
- assert len(all_write_entries) == 21, (
719
- f"Write tool must apply to all 21 hosted hooks, got {len(all_write_entries)}"
718
+ assert len(all_write_entries) == 19, (
719
+ f"Write tool must apply to all 19 hosted hooks, got {len(all_write_entries)}"
720
720
  )
721
721
 
722
722
 
@@ -732,16 +732,16 @@ def test_dispatcher_edit_applies_both_groups() -> None:
732
732
  assert "advisory/refactor_guard.py" in all_edit_script_paths, (
733
733
  "refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
734
734
  )
735
- assert len(all_edit_entries) == 24, (
736
- f"expected 24 Edit entries, got {len(all_edit_entries)}"
735
+ assert len(all_edit_entries) == 22, (
736
+ f"expected 22 Edit entries, got {len(all_edit_entries)}"
737
737
  )
738
738
 
739
739
 
740
740
  def test_dispatcher_multi_edit_applies_only_group_b() -> None:
741
741
  """MultiEdit tool triggers only Group B (10 hooks), not Group A."""
742
742
  all_multi_edit_entries = _applicable_entries_for_tool(MULTI_EDIT_TOOL_NAME)
743
- assert len(all_multi_edit_entries) == 10, (
744
- f"MultiEdit tool must apply to exactly 10 Group-B hooks, got {len(all_multi_edit_entries)}"
743
+ assert len(all_multi_edit_entries) == 9, (
744
+ f"MultiEdit tool must apply to exactly 9 Group-B hooks, got {len(all_multi_edit_entries)}"
745
745
  )
746
746
 
747
747
 
@@ -969,7 +969,6 @@ def test_hosted_hook_set_covers_all_write_edit_blocking_hooks() -> None:
969
969
  "blocking/state_description_blocker.py",
970
970
  "blocking/subprocess_budget_completeness.py",
971
971
  "blocking/hook_prose_detector_consistency.py",
972
- "blocking/verified_commit_message_accuracy_blocker.py",
973
972
  "blocking/workflow_substitution_slot_blocker.py",
974
973
  "blocking/claude_md_orphan_file_blocker.py",
975
974
  "blocking/pytest_testpaths_orphan_blocker.py",
@@ -45,8 +45,9 @@ def test_allows_proactive_status() -> None:
45
45
  assert _should_block(PROACTIVE_STATUS) is False
46
46
 
47
47
 
48
- def test_corrective_message_points_to_show_asset() -> None:
49
- assert "Show-Asset.ps1" in CORRECTIVE_MESSAGE
48
+ def test_corrective_message_points_to_native_windows_app() -> None:
49
+ assert "native Windows app" in CORRECTIVE_MESSAGE
50
+ assert "Show-Asset.ps1" not in CORRECTIVE_MESSAGE
50
51
 
51
52
 
52
53
  def test_corrective_message_names_proactive_escape_hatch() -> None:
@@ -71,7 +72,7 @@ def test_main_blocks_normal_attach() -> None:
71
72
  output_text = _run_main_with_io(json.dumps(hook_input))
72
73
  output = json.loads(output_text)
73
74
  assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
74
- assert "Show-Asset.ps1" in output["hookSpecificOutput"]["permissionDecisionReason"]
75
+ assert "native Windows app" in output["hookSpecificOutput"]["permissionDecisionReason"]
75
76
 
76
77
 
77
78
  def test_main_allows_proactive_attach() -> None:
@@ -30,8 +30,6 @@ _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
30
30
  ALL_CONVERTED_HOOK_FILENAMES = (
31
31
  "open_questions_in_plans_blocker.py",
32
32
  "claude_md_orphan_file_blocker.py",
33
- "pr_converge_bugteam_enforcer.py",
34
- "verdict_directory_write_blocker.py",
35
33
  "package_inventory_stale_blocker.py",
36
34
  )
37
35
 
@@ -14,8 +14,8 @@ Hooks and scripts that collect, store, and query hook-firing records. The pipeli
14
14
  | File | What it does |
15
15
  |---|---|
16
16
  | `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
17
- | `hook_log_extractor.py` | Stop hook reads per-session JSONL transcripts and ingests new `hook_*` attachment records into the `hook_events` table; idempotent via a UNIQUE constraint on `(source_jsonl_path, source_line_number)` |
18
- | `hook_log_stop_wrapper.py` | Thin wrapper that invokes `hook_log_extractor.py` from the Stop lifecycle event |
17
+ | `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
18
+ | `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
19
19
  | `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
20
20
  | `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
21
21
  | `requirements-hook-logs-dev.txt` | Dev/test dependencies |
@@ -38,6 +38,6 @@ The `blocked_commands` view filters to `outcome = 'blocked'`.
38
38
 
39
39
  ## Conventions
40
40
 
41
- - The extractor exits 0 even when Neon is unreachable (offline-graceful); it logs to `OFFLINE_WARNING_LOG` and does not block session end.
41
+ - Extractor and Stop wrapper mains are disabled and exit 0 with no work.
42
42
  - Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
43
43
  - Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
@@ -67,15 +67,10 @@ from hooks_constants.hook_log_extractor_constants import (
67
67
  CATEGORY_PATH_MINIMUM_PARTS,
68
68
  COMMAND_EXCERPT_MAX_CHARACTERS,
69
69
  CONNECT_TIMEOUT_SECONDS,
70
- DEFAULT_QUERY_FOR_SUMMARY,
71
70
  EMPTY_STRING,
72
71
  EXIT_CODE_EXTRACTOR_ENVIRONMENT_MISSING,
73
72
  EXIT_CODE_SUCCESS,
74
73
  EXIT_CODE_UNKNOWN_QUERY,
75
- FLAG_FULL_REBUILD,
76
- FLAG_INCREMENTAL,
77
- FLAG_QUERY,
78
- FLAG_SUMMARY,
79
74
  HOOK_CATEGORY_UNCATEGORIZED,
80
75
  HOOK_EVENTS_INSERT_SQL,
81
76
  HOOK_EVENTS_TRUNCATE_SQL,
@@ -94,10 +89,8 @@ from hooks_constants.hook_log_extractor_constants import (
94
89
  NEON_DATABASE_URL_ENVIRONMENT_VARIABLE,
95
90
  NEWLINE_JOINER,
96
91
  OFFLINE_WARNING_LOG,
97
- OFFSET_STATE_FILE,
98
92
  OFFSETS_JSON_INDENT,
99
93
  OUTCOME_BY_ATTACHMENT_TYPE,
100
- PROJECTS_TRANSCRIPT_ROOT,
101
94
  QUERIES_DIRECTORY_NAME,
102
95
  QUERY_NAME_PATTERN,
103
96
  QUERY_NO_ROWS_RETURNED_MESSAGE,
@@ -874,35 +867,8 @@ def run_query(named_query: str) -> int:
874
867
 
875
868
 
876
869
  def main() -> int:
877
- """Entry point for the hook-log extractor CLI.
878
-
879
- Supported flags:
880
-
881
- * ``--summary`` prints the top blockers of the last twenty-four hours.
882
- * ``--query <name>`` runs a pre-baked SQL file under ``queries/``.
883
- * ``--full-rebuild`` truncates ``hook_events`` and re-reads every
884
- JSONL from byte zero.
885
- * ``--incremental`` is a documented no-op; it selects the default
886
- byte-offset resumption path that the Stop hook also uses when no
887
- flags are passed.
888
- """
889
- all_cli_arguments = list(sys.argv[1:])
890
- if FLAG_SUMMARY in all_cli_arguments:
891
- return run_summary()
892
- if FLAG_QUERY in all_cli_arguments:
893
- flag_index = all_cli_arguments.index(FLAG_QUERY)
894
- if flag_index + 1 >= len(all_cli_arguments):
895
- return run_query(DEFAULT_QUERY_FOR_SUMMARY)
896
- return run_query(all_cli_arguments[flag_index + 1])
897
- is_full_rebuild_requested = FLAG_FULL_REBUILD in all_cli_arguments
898
- is_incremental_requested = FLAG_INCREMENTAL in all_cli_arguments
899
- if is_incremental_requested and is_full_rebuild_requested:
900
- is_full_rebuild_requested = False
901
- return run_full_extraction(
902
- transcripts_root=PROJECTS_TRANSCRIPT_ROOT,
903
- state_file_path=OFFSET_STATE_FILE,
904
- full_rebuild=is_full_rebuild_requested,
905
- )
870
+ """Disabled extractor entry point: exit success with no work."""
871
+ return EXIT_CODE_SUCCESS
906
872
 
907
873
 
908
874
  if __name__ == "__main__":
@@ -1,173 +1,24 @@
1
1
  #!/usr/bin/env python3
2
- """Stop-hook wrapper for hook_log_extractor: debounced, fire-and-forget.
2
+ """Disabled Stop-hook wrapper for the hook-log extractor.
3
3
 
4
- Runs after every assistant turn (Stop hook), so per-turn latency must
5
- stay near zero. The wrapper:
6
-
7
- 1. Reads the last-spawn timestamp; if it falls within the debounce
8
- window, exits 0 immediately without spawning anything (typical fast
9
- path: a small file read, well under 10ms).
10
- 2. Otherwise records the current timestamp, then launches the extractor
11
- as a fully detached background process (no stdio, separate process
12
- group on POSIX or CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP on
13
- Windows) and returns without waiting for it. CREATE_NO_WINDOW
14
- prevents a console flash on Windows even when the wrapper goes
15
- through ``bws run`` first; DETACHED_PROCESS would let the
16
- grandchild python.exe allocate a fresh console.
17
-
18
- Bitwarden injection: when both ``bws`` is on PATH and
19
- ``BWS_ACCESS_TOKEN`` is set, the extractor is launched via
20
- ``bws run --`` so the Neon URL never hits disk; otherwise it is
21
- launched directly. The extractor itself exits 0 when
22
- ``NEON_HOOK_LOGS_DATABASE_URL`` is unset, so missing dependencies
23
- cannot block session shutdown.
24
-
25
- This wrapper always exits 0 so the Stop hook never surfaces a failure.
4
+ The wrapper is off the Stop dispatcher roster and exits 0 without spawning
5
+ the extractor or writing debounce state. Git history holds the prior
6
+ debounce-and-spawn implementation if Neon-backed extraction returns.
26
7
  """
27
8
 
28
9
  from __future__ import annotations
29
10
 
30
- import os
31
- import shutil
32
- import subprocess
33
11
  import sys
34
- import time
35
12
  from pathlib import Path
36
13
 
37
14
  if str(Path(__file__).resolve().parent.parent) not in sys.path:
38
15
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
39
16
 
40
- from hooks_constants.hook_log_extractor_constants import (
41
- BWS_ACCESS_TOKEN_ENV_VAR,
42
- BWS_EXECUTABLE_NAME,
43
- BWS_RUN_SEPARATOR,
44
- BWS_RUN_SUBCOMMAND,
45
- EXIT_CODE_SUCCESS,
46
- FLAG_INCREMENTAL,
47
- STOP_WRAPPER_DEBOUNCE_SECONDS,
48
- STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME,
49
- STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE,
50
- WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG,
51
- WINDOWS_CREATE_NO_WINDOW_FLAG,
52
- WINDOWS_OS_NAME,
53
- )
54
-
55
-
56
- def _extractor_script_path() -> str:
57
- return str(
58
- Path(__file__).resolve().parent / STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME
59
- )
60
-
61
-
62
- def _last_run_timestamp_path() -> Path:
63
- return Path(STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE)
64
-
65
-
66
- def _is_within_debounce_window() -> bool:
67
- timestamp_path = _last_run_timestamp_path()
68
- if not timestamp_path.exists():
69
- return False
70
- try:
71
- previous_timestamp = float(timestamp_path.read_text().strip())
72
- except (OSError, ValueError):
73
- return False
74
- seconds_since_previous_spawn = time.time() - previous_timestamp
75
- if seconds_since_previous_spawn < 0:
76
- return False
77
- return seconds_since_previous_spawn < STOP_WRAPPER_DEBOUNCE_SECONDS
78
-
79
-
80
- def _record_current_timestamp() -> None:
81
- timestamp_path = _last_run_timestamp_path()
82
- timestamp_path.parent.mkdir(parents=True, exist_ok=True)
83
- timestamp_path.write_text(str(time.time()))
84
-
85
-
86
- def _clear_recorded_timestamp() -> None:
87
- """Remove the debounce timestamp regardless of which process wrote it.
88
-
89
- The unlink is unconditional: if process A writes a timestamp, process
90
- B observes it and debounces, then A's spawn fails, A's rollback here
91
- deletes the timestamp B already saw. The next Stop hook then spawns
92
- a fresh extractor instead of debouncing the remainder of the window.
93
- Consequence is benign because the extractor's own offset-file lock
94
- (LOCK_MAXIMUM_RETRY_COUNT in hook_log_extractor_constants) serializes
95
- concurrent extractor runs, so at most one extra extractor briefly
96
- waits on the lock. Scoping the rollback to A's own write would
97
- require a per-process sentinel and tighter atomicity than the
98
- benefit warrants for this Stop-hook fast path.
99
- """
100
- timestamp_path = _last_run_timestamp_path()
101
- timestamp_path.unlink(missing_ok=True)
102
-
103
-
104
- def _can_use_bws() -> bool:
105
- if not os.environ.get(BWS_ACCESS_TOKEN_ENV_VAR):
106
- return False
107
- return shutil.which(BWS_EXECUTABLE_NAME) is not None
108
-
109
-
110
- def _detached_spawn_keyword_arguments() -> dict[str, object]:
111
- spawn_arguments: dict[str, object] = {
112
- "stdin": subprocess.DEVNULL,
113
- "stdout": subprocess.DEVNULL,
114
- "stderr": subprocess.DEVNULL,
115
- "close_fds": True,
116
- }
117
- if os.name == WINDOWS_OS_NAME:
118
- spawn_arguments["creationflags"] = (
119
- WINDOWS_CREATE_NO_WINDOW_FLAG
120
- | WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG
121
- )
122
- startup_info = subprocess.STARTUPINFO()
123
- startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
124
- startup_info.wShowWindow = subprocess.SW_HIDE
125
- spawn_arguments["startupinfo"] = startup_info
126
- else:
127
- spawn_arguments["start_new_session"] = True
128
- return spawn_arguments
129
-
130
-
131
- def _spawn_with_bws() -> None:
132
- subprocess.Popen(
133
- [
134
- BWS_EXECUTABLE_NAME,
135
- BWS_RUN_SUBCOMMAND,
136
- BWS_RUN_SEPARATOR,
137
- sys.executable,
138
- _extractor_script_path(),
139
- FLAG_INCREMENTAL,
140
- ],
141
- **_detached_spawn_keyword_arguments(),
142
- )
143
-
144
-
145
- def _spawn_without_bws() -> None:
146
- subprocess.Popen(
147
- [
148
- sys.executable,
149
- _extractor_script_path(),
150
- FLAG_INCREMENTAL,
151
- ],
152
- **_detached_spawn_keyword_arguments(),
153
- )
17
+ from hooks_constants.hook_log_extractor_constants import EXIT_CODE_SUCCESS
154
18
 
155
19
 
156
20
  def main() -> int:
157
- """Debounce, then fire-and-forget the extractor; always exit 0."""
158
- try:
159
- if _is_within_debounce_window():
160
- return EXIT_CODE_SUCCESS
161
- _record_current_timestamp()
162
- try:
163
- if _can_use_bws():
164
- _spawn_with_bws()
165
- else:
166
- _spawn_without_bws()
167
- except Exception:
168
- _clear_recorded_timestamp()
169
- except Exception:
170
- pass
21
+ """Exit success without spawning the extractor."""
171
22
  return EXIT_CODE_SUCCESS
172
23
 
173
24
 
@@ -805,32 +805,19 @@ def test_offline_fallback_still_exits_zero_when_warning_log_write_raises(
805
805
  assert exit_code == 0
806
806
 
807
807
 
808
- def test_main_accepts_incremental_flag_as_noop(
809
- tmp_path: Path,
808
+ def test_main_is_disabled_and_skips_extraction(
810
809
  monkeypatch: pytest.MonkeyPatch,
811
810
  ) -> None:
812
- """C8: ``--incremental`` must be recognized and route to default extraction."""
813
- captured_arguments: dict[str, object] = {}
814
-
815
- def _fake_run_full_extraction(
816
- transcripts_root: str,
817
- state_file_path: str,
818
- full_rebuild: bool,
819
- ) -> int:
820
- captured_arguments["transcripts_root"] = transcripts_root
821
- captured_arguments["state_file_path"] = state_file_path
822
- captured_arguments["full_rebuild"] = full_rebuild
823
- return 0
824
-
825
- monkeypatch.setattr(sys, "argv", ["hook_log_extractor.py", "--incremental"])
811
+ """Disabled main exits success without running extraction."""
812
+
813
+ def _fail_if_called(*_args: object, **_kwargs: object) -> int:
814
+ raise AssertionError("run_full_extraction must not run while disabled")
815
+
826
816
  monkeypatch.setattr(
827
- hook_log_extractor, "run_full_extraction", _fake_run_full_extraction
817
+ hook_log_extractor, "run_full_extraction", _fail_if_called
828
818
  )
829
819
 
830
- exit_code = hook_log_extractor.main()
831
-
832
- assert exit_code == 0
833
- assert captured_arguments["full_rebuild"] is False
820
+ assert hook_log_extractor.main() == 0
834
821
 
835
822
 
836
823
  def test_run_query_returns_nonzero_for_unknown_query(